<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Chapter 22 Partitioning</title>
<link rel="stylesheet" href="mvl.css" type="text/css" />
<meta name="generator" content="DocBook XSL Stylesheets + chunker.py v1.9.2" />
<link rel="start" href="index.html" title="{book-title}" />
<link rel="up" href="" title="" />
<link rel="prev" href="mysql-innodb-cluster-userguide.html" title="Chapter 21 InnoDB Cluster" />
<link rel="next" href="stored-programs-views.html" title="Chapter 23 Stored Programs and Views" />
</head>
<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
<div class="navheader">
<table width="100%" summary="Navigation header">
<tr>
<th colspan="3" align="center">Chapter 22 Partitioning</th>
</tr>
<tr>
<td width="20%" align="left"><a accesskey="p" href="mysql-innodb-cluster-userguide.html">Prev</a> </td>
<th width="60%" align="center"></th>
<td width="20%" align="right"> <a accesskey="n" href="stored-programs-views.html">Next</a></td>
</tr>
</table>
<hr>
</div>
<div class="chapter">
<div class="titlepage">
<div>
<div>
<h1 class="title"><a name="partitioning"></a>Chapter 22 Partitioning</h1>

</div>

</div>

</div>
<div class="toc">
<p><b>Table of Contents</b></p><dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-overview">22.1 Overview of Partitioning in MySQL</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-types">22.2 Partitioning Types</a></span></dt><dd><dl><dt><span class="section"><a href="partitioning.html#partitioning-range">22.2.1 RANGE Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-list">22.2.2 LIST Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-columns">22.2.3 COLUMNS Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-hash">22.2.4 HASH Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-key">22.2.5 KEY Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-subpartitions">22.2.6 Subpartitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-handling-nulls">22.2.7 How MySQL Partitioning Handles NULL</a></span></dt></dl></dd><dt><span class="section"><a href="partitioning.html#partitioning-management">22.3 Partition Management</a></span></dt><dd><dl><dt><span class="section"><a href="partitioning.html#partitioning-management-range-list">22.3.1 Management of RANGE and LIST Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-management-hash-key">22.3.2 Management of HASH and KEY Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-management-exchange">22.3.3 Exchanging Partitions and Subpartitions with Tables</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-maintenance">22.3.4 Maintenance of Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-info">22.3.5 Obtaining Information About Partitions</a></span></dt></dl></dd><dt><span class="section"><a href="partitioning.html#partitioning-pruning">22.4 Partition Pruning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-selection">22.5 Partition Selection</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-limitations">22.6 Restrictions and Limitations on Partitioning</a></span></dt><dd><dl><dt><span class="section"><a href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys">22.6.1 Partitioning Keys, Primary Keys, and Unique Keys</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-limitations-storage-engines">22.6.2 Partitioning Limitations Relating to Storage Engines</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-limitations-functions">22.6.3 Partitioning Limitations Relating to Functions</a></span></dt></dl></dd></dl>
</div>
<a class="indexterm" name="idm139899484613792"></a><a class="indexterm" name="idm139899484612720"></a><a class="indexterm" name="idm139899484611648"></a><a class="indexterm" name="idm139899484610160"></a><a class="indexterm" name="idm139899484608672"></a><p>
    This chapter discusses <span class="firstterm">user-defined
    partitioning</span>.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
      Table partitioning differs from partitioning as used by window
      functions. For information about window functions, see
      <a class="xref" href="functions.html#window-functions" title="12.20 Window Functions">Section 12.20, “Window Functions”</a>.
</p>
</div>
<p>
    In MySQL 8.0, partitioning support is provided by the
    <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a> storage engine. (The
    <a class="ulink" href="http://dev.mysql.com/doc/refman/5.7/en/mysql-cluster.html" target="_top"><code class="literal">NDB</code></a> storage engine used by MySQL
    Cluster also provides partitioning support, but
    <code class="literal">NDB</code> is not included in MySQL 8.0.)
  </p><p>
    MySQL 8.0 does not currently support partitioning of
    tables using any storage engine other than
    <code class="literal">InnoDB</code>, such as
    <a class="link" href="storage-engines.html#myisam-storage-engine" title="16.2 The MyISAM Storage Engine"><code class="literal">MyISAM</code></a>. An attempt to create a
    partitioned tables using a storage engine that does not supply
    native partitioning support fails with
    <span class="errorcode">ER_CHECK_NOT_IMPLEMENTED</span>.
  </p><p>
    MySQL 8.0 Community binaries provided by Oracle include
    partitioning support provided by the <code class="literal">InnoDB</code>
    storage engine. For information about partitioning support offered
    in MySQL Enterprise Edition binaries, see <a class="xref" href="mysql-enterprise.html" title="Chapter 29 MySQL Enterprise Edition">Chapter 29, <i>MySQL Enterprise Edition</i></a>.
  </p><p>
    If you are compiling MySQL 8.0 from source, configuring
    the build with <code class="literal">InnoDB</code> support is sufficient to
    produce binaries with partition support for
    <code class="literal">InnoDB</code> tables. For more information, see
    <a class="xref" href="installing.html#source-installation" title="2.9 Installing MySQL from Source">Section 2.9, “Installing MySQL from Source”</a>.
  </p><p>
    Nothing further needs to be done to enable partitioning support by
    <code class="literal">InnoDB</code> (for example, no special entries are
    required in the <code class="filename">my.cnf</code> file).
  </p><p>
    It is not possible to disable partitioning support by the
    <code class="literal">InnoDB</code> storage engine.
  </p><p>
    See <a class="xref" href="partitioning.html#partitioning-overview" title="22.1 Overview of Partitioning in MySQL">Section 22.1, “Overview of Partitioning in MySQL”</a>, for an introduction to
    partitioning and partitioning concepts.
  </p><p>
    Several types of partitioning are supported, as well as
    subpartitioning; see <a class="xref" href="partitioning.html#partitioning-types" title="22.2 Partitioning Types">Section 22.2, “Partitioning Types”</a>, and
    <a class="xref" href="partitioning.html#partitioning-subpartitions" title="22.2.6 Subpartitioning">Section 22.2.6, “Subpartitioning”</a>.
  </p><p>
    <a class="xref" href="partitioning.html#partitioning-management" title="22.3 Partition Management">Section 22.3, “Partition Management”</a>, covers methods of adding,
    removing, and altering partitions in existing partitioned tables.
  </p><p>
    <a class="xref" href="partitioning.html#partitioning-maintenance" title="22.3.4 Maintenance of Partitions">Section 22.3.4, “Maintenance of Partitions”</a>, discusses table
    maintenance commands for use with partitioned tables.
  </p><p>
    The <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">PARTITIONS</code></a> table in the
    <code class="literal">INFORMATION_SCHEMA</code> database provides information
    about partitions and partitioned tables. See
    <a class="xref" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table">Section 24.14, “The INFORMATION_SCHEMA PARTITIONS Table”</a>, for more information; for some
    examples of queries against this table, see
    <a class="xref" href="partitioning.html#partitioning-handling-nulls" title="22.2.7 How MySQL Partitioning Handles NULL">Section 22.2.7, “How MySQL Partitioning Handles NULL”</a>.
  </p><p>
    For known issues with partitioning in MySQL 8.0, see
    <a class="xref" href="partitioning.html#partitioning-limitations" title="22.6 Restrictions and Limitations on Partitioning">Section 22.6, “Restrictions and Limitations on Partitioning”</a>.
  </p><p>
    You may also find the following resources to be useful when working
    with partitioned tables.
  </p><a class="indexterm" name="idm139899484578256"></a><p><b>Additional Resources. </b>
      Other sources of information about user-defined partitioning in
      MySQL include the following:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
        <a class="ulink" href="http://forums.mysql.com/list.php?106" target="_top">MySQL Partitioning
        Forum</a>
      </p><p>
        This is the official discussion forum for those interested in or
        experimenting with MySQL Partitioning technology. It features
        announcements and updates from MySQL developers and others. It
        is monitored by members of the Partitioning Development and
        Documentation Teams.
      </p></li><li class="listitem"><p>
        <a class="ulink" href="http://mikaelronstrom.blogspot.com/" target="_top">Mikael
        Ronström's Blog</a>
      </p><p>
        MySQL Partitioning Architect and Lead Developer Mikael Ronström
        frequently posts articles here concerning his work with MySQL
        Partitioning and MySQL Cluster.
      </p></li><li class="listitem"><p>
        <a class="ulink" href="http://www.planetmysql.org/" target="_top">PlanetMySQL</a>
      </p><p>
        A MySQL news site featuring MySQL-related blogs, which should be
        of interest to anyone using my MySQL. We encourage you to check
        here for links to blogs kept by those working with MySQL
        Partitioning, or to have your own blog added to those covered.
</p></li></ul>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-overview"></a>22.1 Overview of Partitioning in MySQL</h2>

</div>

</div>

</div>
<a class="indexterm" name="idm139899484567296"></a><p>
      This section provides a conceptual overview of partitioning in
      MySQL 8.0.
    </p><p>
      For information on partitioning restrictions and feature
      limitations, see <a class="xref" href="partitioning.html#partitioning-limitations" title="22.6 Restrictions and Limitations on Partitioning">Section 22.6, “Restrictions and Limitations on Partitioning”</a>.
    </p><p>
      The SQL standard does not provide much in the way of guidance
      regarding the physical aspects of data storage. The SQL language
      itself is intended to work independently of any data structures or
      media underlying the schemas, tables, rows, or columns with which
      it works. Nonetheless, most advanced database management systems
      have evolved some means of determining the physical location to be
      used for storing specific pieces of data in terms of the file
      system, hardware or even both. In MySQL, the
      <code class="literal">InnoDB</code> storage engine has long supported the
      notion of a tablespace (see <a class="xref" href="innodb-storage-engine.html#innodb-tablespace" title="15.7 InnoDB Tablespaces">Section 15.7, “InnoDB Tablespaces”</a>),
      and the MySQL Server, even prior to the introduction of
      partitioning, could be configured to employ different physical
      directories for storing different databases (see
      <a class="xref" href="optimization.html#symbolic-links" title="8.12.2 Using Symbolic Links">Section 8.12.2, “Using Symbolic Links”</a>, for an explanation of how this
      is done).
    </p><p>
      <span class="firstterm">Partitioning</span> takes this
      notion a step further, by enabling you to distribute portions of
      individual tables across a file system according to rules which
      you can set largely as needed. In effect, different portions of a
      table are stored as separate tables in different locations. The
      user-selected rule by which the division of data is accomplished
      is known as a <span class="firstterm">partitioning
      function</span>, which in MySQL can be the modulus, simple
      matching against a set of ranges or value lists, an internal
      hashing function, or a linear hashing function. The function is
      selected according to the partitioning type specified by the user,
      and takes as its parameter the value of a user-supplied
      expression. This expression can be a column value, a function
      acting on one or more column values, or a set of one or more
      column values, depending on the type of partitioning that is used.
    </p><p>
      In the case of <code class="literal">RANGE</code>, <code class="literal">LIST</code>,
      and [<code class="literal">LINEAR</code>] <code class="literal">HASH</code>
      partitioning, the value of the partitioning column is passed to
      the partitioning function, which returns an integer value
      representing the number of the partition in which that particular
      record should be stored. This function must be nonconstant and
      nonrandom. It may not contain any queries, but may use an SQL
      expression that is valid in MySQL, as long as that expression
      returns either <code class="literal">NULL</code> or an integer
      <em class="replaceable"><code>intval</code></em> such that
    </p><pre data-lang="simple" class="programlisting">
-MAXVALUE &lt;= <em class="replaceable"><code>intval</code></em> &lt;= MAXVALUE
</pre><p>
      (<code class="literal">MAXVALUE</code> is used to represent the least upper
      bound for the type of integer in question.
      <code class="literal">-MAXVALUE</code> represents the greatest lower bound.)
    </p><p>
      For [<code class="literal">LINEAR</code>] <code class="literal">KEY</code>,
      <code class="literal">RANGE COLUMNS</code>, and <code class="literal">LIST
      COLUMNS</code> partitioning, the partitioning expression
      consists of a list of one or more columns.
    </p><p>
      For [<code class="literal">LINEAR</code>] <code class="literal">KEY</code>
      partitioning, the partitioning function is supplied by MySQL.
    </p><p>
      For more information about permitted partitioning column types and
      partitioning functions, see <a class="xref" href="partitioning.html#partitioning-types" title="22.2 Partitioning Types">Section 22.2, “Partitioning Types”</a>,
      as well as <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>, which provides
      partitioning syntax descriptions and additional examples. For
      information about restrictions on partitioning functions, see
      <a class="xref" href="partitioning.html#partitioning-limitations-functions" title="22.6.3 Partitioning Limitations Relating to Functions">Section 22.6.3, “Partitioning Limitations Relating to Functions”</a>.
    </p><p>
      This is known as <span class="firstterm">horizontal
      partitioning</span>—that is, different rows of a table
      may be assigned to different physical partitions. MySQL
      8.0 does not support
      <span class="firstterm">vertical partitioning</span>, in
      which different columns of a table are assigned to different
      physical partitions. There are no plans at this time to introduce
      vertical partitioning into MySQL.
    </p><p>
      For creating partitioned tables, you must use a storage engine
      that supports them. In MySQL 8.0, all partitions of
      the same partitioned table must use the same storage engine.
      However, there is nothing preventing you from using different
      storage engines for different partitioned tables on the same MySQL
      server or even in the same database.
    </p><p>
      In MySQL 8.0, the only storage engine that supports
      partitioning is <code class="literal">InnoDB</code>. Partitioning cannot be
      used with storage engines that do not support it; these include
      the <code class="literal">MyISAM</code>, <code class="literal">MERGE</code>,
      <code class="literal">CSV</code>, and <code class="literal">FEDERATED</code> storage
      engines.
    </p><p>
      When creating a partitioned table, the default storage engine is
      used just as when creating any other table; to override this
      behavior, it is necessary only to use the <code class="literal">[STORAGE]
      ENGINE</code> option just as you would for a table that is not
      partitioned. The target storage engine must provide native
      partitioning support, or the statement fails. You should keep in
      mind that <code class="literal">[STORAGE] ENGINE</code> (and other table
      options) need to be listed <span class="emphasis"><em>before</em></span> any
      partitioning options are used in a <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
      TABLE</code></a> statement. This example shows how to create a
      table that is partitioned by hash into 6 partitions and which uses
      the <code class="literal">InnoDB</code> storage engine (regardless of the
      value of <a class="link" href="server-administration.html#sysvar_default_storage_engine"><code class="literal">default_storage_engine</code></a>):
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)
    ENGINE=INNODB
    PARTITION BY HASH( MONTH(tr_date) )
    PARTITIONS 6;
</pre><p>
      Each <code class="literal">PARTITION</code> clause can include a
      <code class="literal">[STORAGE] ENGINE</code> option, but in MySQL
      8.0 this has no effect.
    </p><p>
      Unless otherwise specified, the remaining examples in this
      discussion assume that
      <a class="link" href="server-administration.html#sysvar_default_storage_engine"><code class="literal">default_storage_engine</code></a> is
      <code class="literal">InnoDB</code>.
</p>
<div class="important" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Important
</div>
<p>
        Partitioning applies to all data and indexes of a table; you
        cannot partition only the data and not the indexes, or vice
        versa, nor can you partition only a portion of the table.
</p>
</div>
<p>
      Data and indexes for each partition can be assigned to a specific
      directory using the <code class="literal">DATA DIRECTORY</code> and
      <code class="literal">INDEX DIRECTORY</code> options for the
      <code class="literal">PARTITION</code> clause of the
      <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement used to
      create the partitioned table.
    </p><p>
      InnoDB supports the <code class="literal">DATA DIRECTORY</code> and
      <code class="literal">INDEX DIRECTORY</code> options for individual
      partitions and subpartitions.
    </p><a class="indexterm" name="idm139899484515648"></a><a class="indexterm" name="idm139899484514160"></a><p>
      All columns used in the table's partitioning expression must
      be part of every unique key that the table may have, including any
      primary key. This means that a table such as this one, created by
      the following SQL statement, cannot be partitioned:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tnp (
    id INT NOT NULL AUTO_INCREMENT,
    ref BIGINT NOT NULL,
    name VARCHAR(255),
    PRIMARY KEY pk (id),
    UNIQUE KEY uk (name)
);
</pre><p>
      Because the keys <code class="literal">pk</code> and <code class="literal">uk</code>
      have no columns in common, there are no columns available for use
      in a partitioning expression. Possible workarounds in this
      situation include adding the <code class="literal">name</code> column to the
      table's primary key, adding the <code class="literal">id</code> column
      to <code class="literal">uk</code>, or simply removing the unique key
      altogether. See
      <a class="xref" href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys" title="22.6.1 Partitioning Keys, Primary Keys, and Unique Keys">Section 22.6.1, “Partitioning Keys, Primary Keys, and Unique Keys”</a>,
      for more information.
    </p><p>
      In addition, <code class="literal">MAX_ROWS</code> and
      <code class="literal">MIN_ROWS</code> can be used to determine the maximum
      and minimum numbers of rows, respectively, that can be stored in
      each partition. See <a class="xref" href="partitioning.html#partitioning-management" title="22.3 Partition Management">Section 22.3, “Partition Management”</a>, for
      more information on these options.
    </p><a class="indexterm" name="idm139899484503232"></a><p>
      Some advantages of partitioning are listed here:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          Partitioning makes it possible to store more data in one table
          than can be held on a single disk or file system partition.
        </p></li><li class="listitem"><p>
          Data that loses its usefulness can often be easily removed
          from a partitioned table by dropping the partition (or
          partitions) containing only that data. Conversely, the process
          of adding new data can in some cases be greatly facilitated by
          adding one or more new partitions for storing specifically
          that data.
        </p></li><li class="listitem"><p>
          Some queries can be greatly optimized in virtue of the fact
          that data satisfying a given <code class="literal">WHERE</code> clause
          can be stored only on one or more partitions, which
          automatically excludes any remaining partitions from the
          search. Because partitions can be altered after a partitioned
          table has been created, you can reorganize your data to
          enhance frequent queries that may not have been often used
          when the partitioning scheme was first set up. This ability to
          exclude non-matching partitions (and thus any rows they
          contain) is often referred to as
          <span class="firstterm">partition pruning</span>. For
          more information, see <a class="xref" href="partitioning.html#partitioning-pruning" title="22.4 Partition Pruning">Section 22.4, “Partition Pruning”</a>.
        </p><p>
          In addition, MySQL 8.0 supports explicit
          partition selection for queries. For example,
          <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT * FROM t
          PARTITION (p0,p1) WHERE c &lt; 5</code></a> selects only those
          rows in partitions <code class="literal">p0</code> and
          <code class="literal">p1</code> that match the <code class="literal">WHERE</code>
          condition. In this case, MySQL does not check any other
          partitions of table <code class="literal">t</code>; this can greatly
          speed up queries when you already know which partition or
          partitions you wish to examine. Partition selection is also
          supported for the data modification statements
          <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a>,
          <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a>,
          <a class="link" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax"><code class="literal">REPLACE</code></a>,
          <a class="link" href="sql-syntax.html#update" title="13.2.12 UPDATE Syntax"><code class="literal">UPDATE</code></a>, and
          <a class="link" href="sql-syntax.html#load-data" title="13.2.7 LOAD DATA INFILE Syntax"><code class="literal">LOAD DATA</code></a>,
          <a class="link" href="sql-syntax.html#load-xml" title="13.2.8 LOAD XML Syntax"><code class="literal">LOAD XML</code></a>. See the descriptions
          of these statements for more information and examples.
</p></li></ul>
</div>
<p>
      Other benefits usually associated with partitioning include those
      in the following list. These features are not currently
      implemented in MySQL Partitioning, but are high on our list of
      priorities.
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          Queries involving aggregate functions such as
          <a class="link" href="functions.html#function_sum"><code class="literal">SUM()</code></a> and
          <a class="link" href="functions.html#function_count"><code class="literal">COUNT()</code></a> can easily be
          parallelized. A simple example of such a query might be
          <code class="literal">SELECT salesperson_id, COUNT(orders) as order_total
          FROM sales GROUP BY salesperson_id;</code>. By
          <span class="quote">“<span class="quote">parallelized,</span>”</span> we mean that the query can be run
          simultaneously on each partition, and the final result
          obtained merely by summing the results obtained for all
          partitions.
        </p></li><li class="listitem"><p>
          Achieving greater query throughput in virtue of spreading data
          seeks over multiple disks.
</p></li></ul>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-types"></a>22.2 Partitioning Types</h2>

</div>

</div>

</div>
<div class="toc">
<dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-range">22.2.1 RANGE Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-list">22.2.2 LIST Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-columns">22.2.3 COLUMNS Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-hash">22.2.4 HASH Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-key">22.2.5 KEY Partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-subpartitions">22.2.6 Subpartitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-handling-nulls">22.2.7 How MySQL Partitioning Handles NULL</a></span></dt></dl>
</div>
<a class="indexterm" name="idm139899484474896"></a><p>
      This section discusses the types of partitioning which are
      available in MySQL 8.0. These include the types
      listed here:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p><b>RANGE partitioning. </b>
            This type of partitioning assigns rows to partitions based
            on column values falling within a given range. See
            <a class="xref" href="partitioning.html#partitioning-range" title="22.2.1 RANGE Partitioning">Section 22.2.1, “RANGE Partitioning”</a>. For information about
            an extension to this type, <code class="literal">RANGE COLUMNS</code>,
            see <a class="xref" href="partitioning.html#partitioning-columns-range" title="22.2.3.1 RANGE COLUMNS partitioning">Section 22.2.3.1, “RANGE COLUMNS partitioning”</a>.
          </p></li><li class="listitem"><p><b>LIST partitioning. </b>
            Similar to partitioning by <code class="literal">RANGE</code>, except
            that the partition is selected based on columns matching one
            of a set of discrete values. See
            <a class="xref" href="partitioning.html#partitioning-list" title="22.2.2 LIST Partitioning">Section 22.2.2, “LIST Partitioning”</a>. For information about
            an extension to this type, <code class="literal">LIST COLUMNS</code>,
            see <a class="xref" href="partitioning.html#partitioning-columns-list" title="22.2.3.2 LIST COLUMNS partitioning">Section 22.2.3.2, “LIST COLUMNS partitioning”</a>.
          </p></li><li class="listitem"><p><b>HASH partitioning. </b>
            With this type of partitioning, a partition is selected
            based on the value returned by a user-defined expression
            that operates on column values in rows to be inserted into
            the table. The function may consist of any expression valid
            in MySQL that yields a nonnegative integer value. An
            extension to this type, <code class="literal">LINEAR HASH</code>, is
            also available. See <a class="xref" href="partitioning.html#partitioning-hash" title="22.2.4 HASH Partitioning">Section 22.2.4, “HASH Partitioning”</a>.
          </p></li><li class="listitem"><p><b>KEY partitioning. </b>
            This type of partitioning is similar to partitioning by
            <code class="literal">HASH</code>, except that only one or more
            columns to be evaluated are supplied, and the MySQL server
            provides its own hashing function. These columns can contain
            other than integer values, since the hashing function
            supplied by MySQL guarantees an integer result regardless of
            the column data type. An extension to this type,
            <code class="literal">LINEAR KEY</code>, is also available. See
            <a class="xref" href="partitioning.html#partitioning-key" title="22.2.5 KEY Partitioning">Section 22.2.5, “KEY Partitioning”</a>.
</p></li></ul>
</div>
<a class="indexterm" name="idm139899484456080"></a><a class="indexterm" name="idm139899484454592"></a><p>
      A very common use of database partitioning is to segregate data by
      date. Some database systems support explicit date partitioning,
      which MySQL does not implement in 8.0. However, it is
      not difficult in MySQL to create partitioning schemes based on
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>,
      <a class="link" href="data-types.html#time" title="11.3.2 The TIME Type"><code class="literal">TIME</code></a>, or
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> columns, or based on
      expressions making use of such columns.
    </p><p>
      When partitioning by <code class="literal">KEY</code> or <code class="literal">LINEAR
      KEY</code>, you can use a <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>,
      <a class="link" href="data-types.html#time" title="11.3.2 The TIME Type"><code class="literal">TIME</code></a>, or
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column as the partitioning
      column without performing any modification of the column value.
      For example, this table creation statement is perfectly valid in
      MySQL:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY KEY(joined)
PARTITIONS 6;
</pre><p>
      In MySQL 8.0, it is also possible to use a
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> or
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column as the partitioning
      column using <code class="literal">RANGE COLUMNS</code> and <code class="literal">LIST
      COLUMNS</code> partitioning.
    </p><p>
      Other partitioning types require a partitioning expression that
      yields an integer value or <code class="literal">NULL</code>. If you wish to
      use date-based partitioning by <code class="literal">RANGE</code>,
      <code class="literal">LIST</code>, <code class="literal">HASH</code>, or
      <code class="literal">LINEAR HASH</code>, you can simply employ a function
      that operates on a <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>,
      <a class="link" href="data-types.html#time" title="11.3.2 The TIME Type"><code class="literal">TIME</code></a>, or
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column and returns such a
      value, as shown here:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);
</pre><p>
      Additional examples of partitioning using dates may be found in
      the following sections of this chapter:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          <a class="xref" href="partitioning.html#partitioning-range" title="22.2.1 RANGE Partitioning">Section 22.2.1, “RANGE Partitioning”</a>
        </p></li><li class="listitem"><p>
          <a class="xref" href="partitioning.html#partitioning-hash" title="22.2.4 HASH Partitioning">Section 22.2.4, “HASH Partitioning”</a>
        </p></li><li class="listitem"><p>
          <a class="xref" href="partitioning.html#partitioning-linear-hash" title="22.2.4.1 LINEAR HASH Partitioning">Section 22.2.4.1, “LINEAR HASH Partitioning”</a>
</p></li></ul>
</div>
<p>
      For more complex examples of date-based partitioning, see the
      following sections:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          <a class="xref" href="partitioning.html#partitioning-pruning" title="22.4 Partition Pruning">Section 22.4, “Partition Pruning”</a>
        </p></li><li class="listitem"><p>
          <a class="xref" href="partitioning.html#partitioning-subpartitions" title="22.2.6 Subpartitioning">Section 22.2.6, “Subpartitioning”</a>
</p></li></ul>
</div>
<p>
      MySQL partitioning is optimized for use with the
      <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS()</code></a>,
      <a class="link" href="functions.html#function_year"><code class="literal">YEAR()</code></a>, and
      <a class="link" href="functions.html#function_to-seconds"><code class="literal">TO_SECONDS()</code></a> functions. However,
      you can use other date and time functions that return an integer
      or <code class="literal">NULL</code>, such as
      <a class="link" href="functions.html#function_weekday"><code class="literal">WEEKDAY()</code></a>,
      <a class="link" href="functions.html#function_dayofyear"><code class="literal">DAYOFYEAR()</code></a>, or
      <a class="link" href="functions.html#function_month"><code class="literal">MONTH()</code></a>. See
      <a class="xref" href="functions.html#date-and-time-functions" title="12.7 Date and Time Functions">Section 12.7, “Date and Time Functions”</a>, for more information
      about such functions.
    </p><p>
      It is important to remember—regardless of the type of
      partitioning that you use—that partitions are always
      numbered automatically and in sequence when created, starting with
      <code class="literal">0</code>. When a new row is inserted into a
      partitioned table, it is these partition numbers that are used in
      identifying the correct partition. For example, if your table uses
      4 partitions, these partitions are numbered <code class="literal">0</code>,
      <code class="literal">1</code>, <code class="literal">2</code>, and
      <code class="literal">3</code>. For the <code class="literal">RANGE</code> and
      <code class="literal">LIST</code> partitioning types, it is necessary to
      ensure that there is a partition defined for each partition
      number. For <code class="literal">HASH</code> partitioning, the
      user-supplied expression must evaluate to an integer value greater
      than <code class="literal">0</code>. For <code class="literal">KEY</code>
      partitioning, this issue is taken care of automatically by the
      hashing function which the MySQL server employs internally.
    </p><p>
      Names of partitions generally follow the rules governing other
      MySQL identifiers, such as those for tables and databases.
      However, you should note that partition names are not
      case-sensitive. For example, the following
      <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement fails as
      shown:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t2 (val INT)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LIST(val)(</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION mypart VALUES IN (1,3,5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION MyPart VALUES IN (2,4,6)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
ERROR 1488 (HY000): Duplicate partition name mypart
</pre><p>
      Failure occurs because MySQL sees no difference between the
      partition names <code class="literal">mypart</code> and
      <code class="literal">MyPart</code>.
    </p><p>
      When you specify the number of partitions for the table, this must
      be expressed as a positive, nonzero integer literal with no
      leading zeros, and may not be an expression such as
      <code class="literal">0.8E+01</code> or <code class="literal">6-2</code>, even if it
      evaluates to an integer value. Decimal fractions are not
      permitted.
    </p><p>
      In the sections that follow, we do not necessarily provide all
      possible forms for the syntax that can be used for creating each
      partition type; for this information, see
      <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>.
</p>
<div class="section">

<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-range"></a>22.2.1 RANGE Partitioning</h3>
</div>
</div>
</div>
<a class="indexterm" name="idm139899484388720"></a><a class="indexterm" name="idm139899484387232"></a><p>
        A table that is partitioned by range is partitioned in such a
        way that each partition contains rows for which the partitioning
        expression value lies within a given range. Ranges should be
        contiguous but not overlapping, and are defined using the
        <code class="literal">VALUES LESS THAN</code> operator. For the next few
        examples, suppose that you are creating a table such as the
        following to hold personnel records for a chain of 20 video
        stores, numbered 1 through 20:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
);
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          The <code class="literal">employees</code> table used here has no
          primary or unique keys. While the examples work as shown for
          purposes of the present discussion, you should keep in mind
          that tables are extremely likely in practice to have primary
          keys, unique keys, or both, and that allowable choices for
          partitioning columns depend on the columns used for these
          keys, if any are present. For a discussion of these issues,
          see
          <a class="xref" href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys" title="22.6.1 Partitioning Keys, Primary Keys, and Unique Keys">Section 22.6.1, “Partitioning Keys, Primary Keys, and Unique Keys”</a>.
</p>
</div>
<p>
        This table can be partitioned by range in a number of ways,
        depending on your needs. One way would be to use the
        <code class="literal">store_id</code> column. For instance, you might
        decide to partition the table 4 ways by adding a
        <code class="literal">PARTITION BY RANGE</code> clause as shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    PARTITION p3 VALUES LESS THAN (21)
);
</pre><p>
        In this partitioning scheme, all rows corresponding to employees
        working at stores 1 through 5 are stored in partition
        <code class="literal">p0</code>, to those employed at stores 6 through 10
        are stored in partition <code class="literal">p1</code>, and so on. Each
        partition is defined in order, from lowest to highest. This is a
        requirement of the <code class="literal">PARTITION BY RANGE</code> syntax;
        you can think of it as being analogous to a series of
        <code class="literal">if ... elseif ...</code> statements in C or Java in
        this regard.
      </p><p>
        It is easy to determine that a new row containing the data
        <code class="literal">(72, 'Mitchell', 'Wilson', '1998-06-25', NULL,
        13)</code> is inserted into partition <code class="literal">p2</code>,
        but what happens when your chain adds a
        21<sup>st</sup> store? Under this scheme, there
        is no rule that covers a row whose <code class="literal">store_id</code>
        is greater than 20, so an error results because the server does
        not know where to place it. You can keep this from occurring by
        using a <span class="quote">“<span class="quote">catchall</span>”</span> <code class="literal">VALUES LESS
        THAN</code> clause in the <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
        TABLE</code></a> statement that provides for all values greater
        than the highest value explicitly named:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    <span class="emphasis"><em>PARTITION p3 VALUES LESS THAN MAXVALUE</em></span>
);
</pre><p>
        (As with the other examples in this chapter, we assume that the
        default storage engine is <code class="literal">InnoDB</code>.)
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          Another way to avoid an error when no matching value is found
          is to use the <code class="literal">IGNORE</code> keyword as part of the
          <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statement. For an
          example, see <a class="xref" href="partitioning.html#partitioning-list" title="22.2.2 LIST Partitioning">Section 22.2.2, “LIST Partitioning”</a>. Also see
          <a class="xref" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax">Section 13.2.6, “INSERT Syntax”</a>, for general information about
          <code class="literal">IGNORE</code>.
</p>
</div>
<p>
        <code class="literal">MAXVALUE</code> represents an integer value that is
        always greater than the largest possible integer value (in
        mathematical language, it serves as a
        <span class="firstterm">least upper bound</span>). Now,
        any rows whose <code class="literal">store_id</code> column value is
        greater than or equal to 16 (the highest value defined) are
        stored in partition <code class="literal">p3</code>. At some point in the
        future—when the number of stores has increased to 25, 30,
        or more—you can use an
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement to add new partitions for stores
        21-25, 26-30, and so on (see
        <a class="xref" href="partitioning.html#partitioning-management" title="22.3 Partition Management">Section 22.3, “Partition Management”</a>, for details of how to
        do this).
      </p><p>
        In much the same fashion, you could partition the table based on
        employee job codes—that is, based on ranges of
        <code class="literal">job_code</code> column values. For
        example—assuming that two-digit job codes are used for
        regular (in-store) workers, three-digit codes are used for
        office and support personnel, and four-digit codes are used for
        management positions—you could create the partitioned
        table using the following statement:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
    PARTITION p0 VALUES LESS THAN (100),
    PARTITION p1 VALUES LESS THAN (1000),
    PARTITION p2 VALUES LESS THAN (10000)
);
</pre><p>
        In this instance, all rows relating to in-store workers would be
        stored in partition <code class="literal">p0</code>, those relating to
        office and support staff in <code class="literal">p1</code>, and those
        relating to managers in partition <code class="literal">p2</code>.
      </p><p>
        It is also possible to use an expression in <code class="literal">VALUES LESS
        THAN</code> clauses. However, MySQL must be able to evaluate
        the expression's return value as part of a <code class="literal">LESS
        THAN</code> (<code class="literal">&lt;</code>) comparison.
      </p><a class="indexterm" name="idm139899484345360"></a><p>
        Rather than splitting up the table data according to store
        number, you can use an expression based on one of the two
        <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> columns instead. For
        example, let us suppose that you wish to partition based on the
        year that each employee left the company; that is, the value of
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(separated)</code></a>. An example of a
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement that
        implements such a partitioning scheme is shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY RANGE ( YEAR(separated) ) (
    PARTITION p0 VALUES LESS THAN (1991),
    PARTITION p1 VALUES LESS THAN (1996),
    PARTITION p2 VALUES LESS THAN (2001),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);
</pre><p>
        In this scheme, for all employees who left before 1991, the rows
        are stored in partition <code class="literal">p0</code>; for those who
        left in the years 1991 through 1995, in <code class="literal">p1</code>;
        for those who left in the years 1996 through 2000, in
        <code class="literal">p2</code>; and for any workers who left after the
        year 2000, in <code class="literal">p3</code>.
      </p><p>
        It is also possible to partition a table by
        <code class="literal">RANGE</code>, based on the value of a
        <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">TIMESTAMP</code></a> column, using the
        <a class="link" href="functions.html#function_unix-timestamp"><code class="literal">UNIX_TIMESTAMP()</code></a> function, as
        shown in this example:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE quarterly_report_status (
    report_id INT NOT NULL,
    report_status VARCHAR(20) NOT NULL,
    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
    PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
        Any other expressions involving
        <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">TIMESTAMP</code></a> values are not
        permitted. (See Bug #42849.)
      </p><p>
        Range partitioning is particularly useful when one or more of
        the following conditions is true:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            You want or need to delete <span class="quote">“<span class="quote">old</span>”</span> data. If you
            are using the partitioning scheme shown previously for the
            <code class="literal">employees</code> table, you can simply use
            <code class="literal">ALTER TABLE employees DROP PARTITION p0;</code>
            to delete all rows relating to employees who stopped working
            for the firm prior to 1991. (See
            <a class="xref" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax">Section 13.1.8, “ALTER TABLE Syntax”</a>, and
            <a class="xref" href="partitioning.html#partitioning-management" title="22.3 Partition Management">Section 22.3, “Partition Management”</a>, for more
            information.) For a table with a great many rows, this can
            be much more efficient than running a
            <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a> query such as
            <code class="literal">DELETE FROM employees WHERE YEAR(separated) &lt;=
            1990;</code>.
          </p></li><li class="listitem"><p>
            You want to use a column containing date or time values, or
            containing values arising from some other series.
          </p></li><li class="listitem"><p>
            You frequently run queries that depend directly on the
            column used for partitioning the table. For example, when
            executing a query such as
            <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN SELECT
            COUNT(*) FROM employees WHERE separated BETWEEN '2000-01-01'
            AND '2000-12-31' GROUP BY store_id;</code></a>, MySQL can
            quickly determine that only partition <code class="literal">p2</code>
            needs to be scanned because the remaining partitions cannot
            contain any records satisfying the <code class="literal">WHERE</code>
            clause. See <a class="xref" href="partitioning.html#partitioning-pruning" title="22.4 Partition Pruning">Section 22.4, “Partition Pruning”</a>, for more
            information about how this is accomplished.
</p></li></ul>
</div>
<p>
        A variant on this type of partitioning is <code class="literal">RANGE
        COLUMNS</code> partitioning. Partitioning by <code class="literal">RANGE
        COLUMNS</code> makes it possible to employ multiple columns
        for defining partitioning ranges that apply both to placement of
        rows in partitions and for determining the inclusion or
        exclusion of specific partitions when performing partition
        pruning. See <a class="xref" href="partitioning.html#partitioning-columns-range" title="22.2.3.1 RANGE COLUMNS partitioning">Section 22.2.3.1, “RANGE COLUMNS partitioning”</a>, for
        more information.
      </p><p><a name="partitioning-time-intervals"></a><b>Partitioning schemes based on time intervals. </b>
          If you wish to implement a partitioning scheme based on ranges
          or intervals of time in MySQL 8.0, you have two
          options:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
            Partition the table by <code class="literal">RANGE</code>, and for the
            partitioning expression, employ a function operating on a
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>,
            <a class="link" href="data-types.html#time" title="11.3.2 The TIME Type"><code class="literal">TIME</code></a>, or
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column and returning
            an integer value, as shown here:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);
</pre><p>
            In MySQL 8.0, it is also possible to partition
            a table by <code class="literal">RANGE</code> based on the value of a
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">TIMESTAMP</code></a> column, using the
            <a class="link" href="functions.html#function_unix-timestamp"><code class="literal">UNIX_TIMESTAMP()</code></a> function, as
            shown in this example:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE quarterly_report_status (
    report_id INT NOT NULL,
    report_status VARCHAR(20) NOT NULL,
    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
    PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
            In MySQL 8.0, any other expressions involving
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">TIMESTAMP</code></a> values are not
            permitted. (See Bug #42849.)
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
              It is also possible in MySQL 8.0 to use
              <a class="link" href="functions.html#function_unix-timestamp"><code class="literal">UNIX_TIMESTAMP(timestamp_column)</code></a>
              as a partitioning expression for tables that are
              partitioned by <code class="literal">LIST</code>. However, it is
              usually not practical to do so.
</p>
</div>
</li><li class="listitem"><p>
            Partition the table by <code class="literal">RANGE COLUMNS</code>,
            using a <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> or
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column as the
            partitioning column. For example, the
            <code class="literal">members</code> table could be defined using the
            <code class="literal">joined</code> column directly, as shown here:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE COLUMNS(joined) (
    PARTITION p0 VALUES LESS THAN ('1960-01-01'),
    PARTITION p1 VALUES LESS THAN ('1970-01-01'),
    PARTITION p2 VALUES LESS THAN ('1980-01-01'),
    PARTITION p3 VALUES LESS THAN ('1990-01-01'),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);
</pre></li></ol>
</div>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">
<div class="admon-title">
Note
</div>
<p>
          The use of partitioning columns employing date or time types
          other than <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> or
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> is not supported with
          <code class="literal">RANGE COLUMNS</code>.
</p>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-list"></a>22.2.2 LIST Partitioning</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899484279312"></a><a class="indexterm" name="idm139899484277824"></a><p>
        List partitioning in MySQL is similar to range partitioning in
        many ways. As in partitioning by <code class="literal">RANGE</code>, each
        partition must be explicitly defined. The chief difference
        between the two types of partitioning is that, in list
        partitioning, each partition is defined and selected based on
        the membership of a column value in one of a set of value lists,
        rather than in one of a set of contiguous ranges of values. This
        is done by using <code class="literal">PARTITION BY
        LIST(<em class="replaceable"><code>expr</code></em>)</code> where
        <em class="replaceable"><code>expr</code></em> is a column value or an
        expression based on a column value and returning an integer
        value, and then defining each partition by means of a
        <code class="literal">VALUES IN
        (<em class="replaceable"><code>value_list</code></em>)</code>, where
        <em class="replaceable"><code>value_list</code></em> is a comma-separated list
        of integers.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          In MySQL 8.0, it is possible to match against
          only a list of integers (and possibly
          <code class="literal">NULL</code>—see
          <a class="xref" href="partitioning.html#partitioning-handling-nulls" title="22.2.7 How MySQL Partitioning Handles NULL">Section 22.2.7, “How MySQL Partitioning Handles NULL”</a>) when
          partitioning by <code class="literal">LIST</code>.
        </p><p>
          However, other column types may be used in value lists when
          employing <code class="literal">LIST COLUMN</code> partitioning, which
          is described later in this section.
</p>
</div>
<p>
        Unlike the case with partitions defined by range, list
        partitions do not need to be declared in any particular order.
        For more detailed syntactical information, see
        <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>.
      </p><p>
        For the examples that follow, we assume that the basic
        definition of the table to be partitioned is provided by the
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement shown
        here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
);
</pre><p>
        (This is the same table used as a basis for the examples in
        <a class="xref" href="partitioning.html#partitioning-range" title="22.2.1 RANGE Partitioning">Section 22.2.1, “RANGE Partitioning”</a>. As with the other
        partitioning examples, we assume that the
        <a class="link" href="server-administration.html#sysvar_default_storage_engine"><code class="literal">default_storage_engine</code></a> is
        <code class="literal">InnoDB</code>.)
      </p><p>
        Suppose that there are 20 video stores distributed among 4
        franchises as shown in the following table.
</p>
<div class="informaltable">
<table summary="An example of 20 video stores distributed among 4 regional franchises, as described in the preceding text."><col width="30%"><col width="70%"><thead><tr>
            <th scope="col">Region</th>
            <th scope="col">Store ID Numbers</th>
          </tr></thead><tbody><tr>
            <td scope="row">North</td>
            <td>3, 5, 6, 9, 17</td>
          </tr><tr>
            <td scope="row">East</td>
            <td>1, 2, 10, 11, 19, 20</td>
          </tr><tr>
            <td scope="row">West</td>
            <td>4, 12, 13, 14, 18</td>
          </tr><tr>
            <td scope="row">Central</td>
            <td>7, 8, 15, 16</td>
</tr></tbody></table>
</div>
<p>
        To partition this table in such a way that rows for stores
        belonging to the same region are stored in the same partition,
        you could use the <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a>
        statement shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (3,5,6,9,17),
    PARTITION pEast VALUES IN (1,2,10,11,19,20),
    PARTITION pWest VALUES IN (4,12,13,14,18),
    PARTITION pCentral VALUES IN (7,8,15,16)
);
</pre><p>
        This makes it easy to add or drop employee records relating to
        specific regions to or from the table. For instance, suppose
        that all stores in the West region are sold to another company.
        In MySQL 8.0, all rows relating to employees
        working at stores in that region can be deleted with the query
        <code class="literal">ALTER TABLE employees TRUNCATE PARTITION
        pWest</code>, which can be executed much more efficiently
        than the equivalent <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a>
        statement <code class="literal">DELETE FROM employees WHERE store_id IN
        (4,12,13,14,18);</code>. (Using <code class="literal">ALTER TABLE
        employees DROP PARTITION pWest</code> would also delete all
        of these rows, but would also remove the partition
        <code class="literal">pWest</code> from the definition of the table; you
        would need to use an <code class="literal">ALTER TABLE ... ADD
        PARTITION</code> statement to restore the table's
        original partitioning scheme.)
      </p><p>
        As with <code class="literal">RANGE</code> partitioning, it is possible to
        combine <code class="literal">LIST</code> partitioning with partitioning
        by hash or key to produce a composite partitioning
        (subpartitioning). See
        <a class="xref" href="partitioning.html#partitioning-subpartitions" title="22.2.6 Subpartitioning">Section 22.2.6, “Subpartitioning”</a>.
      </p><p>
        Unlike the case with <code class="literal">RANGE</code> partitioning,
        there is no <span class="quote">“<span class="quote">catch-all</span>”</span> such as
        <code class="literal">MAXVALUE</code>; all expected values for the
        partitioning expression should be covered in <code class="literal">PARTITION
        ... VALUES IN (...)</code> clauses. An
        <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statement containing an
        unmatched partitioning column value fails with an error, as
        shown in this example:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE h2 (</code></strong>
    -&gt;   <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;   <strong class="userinput"><code>c2 INT</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LIST(c1) (</code></strong>
    -&gt;   <strong class="userinput"><code>PARTITION p0 VALUES IN (1, 4, 7),</code></strong>
    -&gt;   <strong class="userinput"><code>PARTITION p1 VALUES IN (2, 5, 8)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.11 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO h2 VALUES (3, 5);</code></strong>
<span class="errortext">ERROR 1525 (HY000): Table has no partition for value 3</span>
</pre><p>
        When inserting multiple rows using a single
        <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statement into a single
        <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a> table,
        <code class="literal">InnoDB</code> considers the statement a single
        transaction, so that the presence of any unmatched values causes
        the statement to fail completely, and so no rows are inserted.
      </p><p>
        You can cause this type of error to be ignored by using the
        <code class="literal">IGNORE</code> keyword. If you do so, rows containing
        unmatched partitioning column values are not inserted, but any
        rows with matching values <span class="emphasis"><em>are</em></span> inserted, and
        no errors are reported:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>TRUNCATE h2;</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM h2;</code></strong>
Empty set (0.00 sec)

mysql&gt; <strong class="userinput"><code>INSERT IGNORE INTO h2 VALUES (2, 5), (6, 10), (7, 5), (3, 1), (1, 9);</code></strong>
Query OK, 3 rows affected (0.00 sec)
Records: 5  Duplicates: 2  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT * FROM h2;</code></strong>
+------+------+
| c1   | c2   |
+------+------+
|    7 |    5 |
|    1 |    9 |
|    2 |    5 |
+------+------+
3 rows in set (0.00 sec)
</pre><p>
        MySQL 8.0 also provides support for <code class="literal">LIST
        COLUMNS</code> partitioning, a variant of
        <code class="literal">LIST</code> partitioning that enables you to use
        columns of types other than integer types for partitioning
        columns, and to use multiple columns as partitioning keys. For
        more information, see
        <a class="xref" href="partitioning.html#partitioning-columns-list" title="22.2.3.2 LIST COLUMNS partitioning">Section 22.2.3.2, “LIST COLUMNS partitioning”</a>.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-columns"></a>22.2.3 COLUMNS Partitioning</h3>

</div>

</div>

</div>
<div class="toc">
<dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-columns-range">22.2.3.1 RANGE COLUMNS partitioning</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-columns-list">22.2.3.2 LIST COLUMNS partitioning</a></span></dt></dl>
</div>
<a class="indexterm" name="idm139899484206112"></a><a class="indexterm" name="idm139899484204624"></a><a class="indexterm" name="idm139899484203552"></a><a class="indexterm" name="idm139899484202464"></a><a class="indexterm" name="idm139899484201376"></a><a class="indexterm" name="idm139899484200304"></a><p>
        The next two sections discuss
        <span class="firstterm"><code class="literal">COLUMNS</code>
        partitioning</span>, which are variants on
        <code class="literal">RANGE</code> and <code class="literal">LIST</code>
        partitioning. <code class="literal">COLUMNS</code> partitioning enables
        the use of multiple columns in partitioning keys. All of these
        columns are taken into account both for the purpose of placing
        rows in partitions and for the determination of which partitions
        are to be checked for matching rows in partition pruning.
      </p><p>
        In addition, both <code class="literal">RANGE COLUMNS</code> partitioning
        and <code class="literal">LIST COLUMNS</code> partitioning support the use
        of non-integer columns for defining value ranges or list
        members. The permitted data types are shown in the following
        list:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            All integer types: <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">TINYINT</code></a>,
            <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">SMALLINT</code></a>,
            <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">MEDIUMINT</code></a>,
            <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">INT</code></a>
            (<a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">INTEGER</code></a>), and
            <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">BIGINT</code></a>. (This is the same as
            with partitioning by <code class="literal">RANGE</code> and
            <code class="literal">LIST</code>.)
          </p><p>
            Other numeric data types (such as
            <a class="link" href="data-types.html#fixed-point-types" title="11.2.2 Fixed-Point Types (Exact Value) - DECIMAL, NUMERIC"><code class="literal">DECIMAL</code></a> or
            <a class="link" href="data-types.html#floating-point-types" title="11.2.3 Floating-Point Types (Approximate Value) - FLOAT, DOUBLE"><code class="literal">FLOAT</code></a>) are not supported as
            partitioning columns.
          </p></li><li class="listitem"><p>
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> and
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a>.
          </p><p>
            Columns using other data types relating to dates or times
            are not supported as partitioning columns.
          </p></li><li class="listitem"><p>
            The following string types:
            <a class="link" href="data-types.html#char" title="11.4.1 The CHAR and VARCHAR Types"><code class="literal">CHAR</code></a>,
            <a class="link" href="data-types.html#char" title="11.4.1 The CHAR and VARCHAR Types"><code class="literal">VARCHAR</code></a>,
            <a class="link" href="data-types.html#binary-varbinary" title="11.4.2 The BINARY and VARBINARY Types"><code class="literal">BINARY</code></a>, and
            <a class="link" href="data-types.html#binary-varbinary" title="11.4.2 The BINARY and VARBINARY Types"><code class="literal">VARBINARY</code></a>.
          </p><p>
            <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">TEXT</code></a> and
            <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">BLOB</code></a> columns are not
            supported as partitioning columns.
</p></li></ul>
</div>
<p>
        The discussions of <code class="literal">RANGE COLUMNS</code> and
        <code class="literal">LIST COLUMNS</code> partitioning in the next two
        sections assume that you are already familiar with partitioning
        based on ranges and lists as supported in MySQL 5.1 and later;
        for more information about these, see
        <a class="xref" href="partitioning.html#partitioning-range" title="22.2.1 RANGE Partitioning">Section 22.2.1, “RANGE Partitioning”</a>, and
        <a class="xref" href="partitioning.html#partitioning-list" title="22.2.2 LIST Partitioning">Section 22.2.2, “LIST Partitioning”</a>, respectively.
</p>
<div class="section">

<div class="titlepage">
<div>
<div>
<h4 class="title"><a name="partitioning-columns-range"></a>22.2.3.1 RANGE COLUMNS partitioning</h4>
</div>
</div>
</div>
<p>
          Range columns partitioning is similar to range partitioning,
          but enables you to define partitions using ranges based on
          multiple column values. In addition, you can define the ranges
          using columns of types other than integer types.
        </p><p>
          <code class="literal">RANGE COLUMNS</code> partitioning differs
          significantly from <code class="literal">RANGE</code> partitioning in
          the following ways:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
              <code class="literal">RANGE COLUMNS</code> does not accept
              expressions, only names of columns.
            </p></li><li class="listitem"><p>
              <code class="literal">RANGE COLUMNS</code> accepts a list of one or
              more columns.
            </p><p>
              <code class="literal">RANGE COLUMNS</code> partitions are based on
              comparisons between
              <span class="firstterm">tuples</span> (lists of
              column values) rather than comparisons between scalar
              values. Placement of rows in <code class="literal">RANGE
              COLUMNS</code> partitions is also based on comparisons
              between tuples; this is discussed further later in this
              section.
            </p></li><li class="listitem"><p>
              <code class="literal">RANGE COLUMNS</code> partitioning columns are
              not restricted to integer columns; string,
              <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> and
              <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> columns can also
              be used as partitioning columns. (See
              <a class="xref" href="partitioning.html#partitioning-columns" title="22.2.3 COLUMNS Partitioning">Section 22.2.3, “COLUMNS Partitioning”</a>, for details.)
</p></li></ul>
</div>
<p>
          The basic syntax for creating a table partitioned by
          <code class="literal">RANGE COLUMNS</code> is shown here:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE <em class="replaceable"><code>table_name</code></em>
PARTITIONED BY RANGE COLUMNS(<em class="replaceable"><code>column_list</code></em>) (
    PARTITION <em class="replaceable"><code>partition_name</code></em> VALUES LESS THAN (<em class="replaceable"><code>value_list</code></em>)[,
    PARTITION <em class="replaceable"><code>partition_name</code></em> VALUES LESS THAN (<em class="replaceable"><code>value_list</code></em>)][,
    ...]
)

<em class="replaceable"><code>column_list</code></em>:
    <em class="replaceable"><code>column_name</code></em>[, <em class="replaceable"><code>column_name</code></em>][, ...]

<em class="replaceable"><code>value_list</code></em>:
    <em class="replaceable"><code>value</code></em>[, <em class="replaceable"><code>value</code></em>][, ...]
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
            Not all <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> options
            that can be used when creating partitioned tables are shown
            here. For complete information, see
            <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>.
</p>
</div>
<p>
          In the syntax just shown,
          <em class="replaceable"><code>column_list</code></em> is a list of one or
          more columns (sometimes called a
          <span class="firstterm">partitioning column
          list</span>), and <em class="replaceable"><code>value_list</code></em> is
          a list of values (that is, it is a
          <span class="firstterm">partition definition value
          list</span>). A <em class="replaceable"><code>value_list</code></em> must
          be supplied for each partition definition, and each
          <em class="replaceable"><code>value_list</code></em> must have the same
          number of values as the <em class="replaceable"><code>column_list</code></em>
          has columns. Generally speaking, if you use
          <em class="replaceable"><code>N</code></em> columns in the
          <code class="literal">COLUMNS</code> clause, then each <code class="literal">VALUES
          LESS THAN</code> clause must also be supplied with a list
          of <em class="replaceable"><code>N</code></em> values.
        </p><p>
          The elements in the partitioning column list and in the value
          list defining each partition must occur in the same order. In
          addition, each element in the value list must be of the same
          data type as the corresponding element in the column list.
          However, the order of the column names in the partitioning
          column list and the value lists does not have to be the same
          as the order of the table column definitions in the main part
          of the <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement.
          As with table partitioned by <code class="literal">RANGE</code>, you can
          use <code class="literal">MAXVALUE</code> to represent a value such that
          any legal value inserted into a given column is always less
          than this value. Here is an example of a
          <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement that
          helps to illustrate all of these points:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE rcx (</code></strong>
    -&gt;     <strong class="userinput"><code>a INT,</code></strong>
    -&gt;     <strong class="userinput"><code>b INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c CHAR(3),</code></strong>
    -&gt;     <strong class="userinput"><code>d INT</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE COLUMNS(a,d,c) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (5,10,'ggg'),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (10,20,'mmm'),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (15,30,'sss'),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.15 sec)
</pre><p>
          Table <code class="literal">rcx</code> contains the columns
          <code class="literal">a</code>, <code class="literal">b</code>,
          <code class="literal">c</code>, <code class="literal">d</code>. The partitioning
          column list supplied to the <code class="literal">COLUMNS</code> clause
          uses 3 of these columns, in the order <code class="literal">a</code>,
          <code class="literal">d</code>, <code class="literal">c</code>. Each value list
          used to define a partition contains 3 values in the same
          order; that is, each value list tuple has the form
          (<code class="literal">INT</code>, <code class="literal">INT</code>,
          <code class="literal">CHAR(3)</code>), which corresponds to the data
          types used by columns <code class="literal">a</code>,
          <code class="literal">d</code>, and <code class="literal">c</code> (in that
          order).
        </p><p>
          Placement of rows into partitions is determined by comparing
          the tuple from a row to be inserted that matches the column
          list in the <code class="literal">COLUMNS</code> clause with the tuples
          used in the <code class="literal">VALUES LESS THAN</code> clauses to
          define partitions of the table. Because we are comparing
          tuples (that is, lists or sets of values) rather than scalar
          values, the semantics of <code class="literal">VALUES LESS THAN</code>
          as used with <code class="literal">RANGE COLUMNS</code> partitions
          differs somewhat from the case with simple
          <code class="literal">RANGE</code> partitions. In
          <code class="literal">RANGE</code> partitioning, a row generating an
          expression value that is equal to a limiting value in a
          <code class="literal">VALUES LESS THAN</code> is never placed in the
          corresponding partition; however, when using <code class="literal">RANGE
          COLUMNS</code> partitioning, it is sometimes possible for a
          row whose partitioning column list's first element is
          equal in value to the that of the first element in a
          <code class="literal">VALUES LESS THAN</code> value list to be placed in
          the corresponding partition.
        </p><p>
          Consider the <code class="literal">RANGE</code> partitioned table
          created by this statement:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE r1 (
    a INT,
    b INT
)
PARTITION BY RANGE (a)  (
    PARTITION p0 VALUES LESS THAN (5),
    PARTITION p1 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
          If we insert 3 rows into this table such that the column value
          for <code class="literal">a</code> is <code class="literal">5</code> for each row,
          all 3 rows are stored in partition <code class="literal">p1</code>
          because the <code class="literal">a</code> column value is in each case
          not less than 5, as we can see by executing the proper query
          against the
          <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
          table:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO r1 VALUES (5,10), (5,11), (5,12);</code></strong>
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME,TABLE_ROWS</code></strong>
    -&gt;     <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE TABLE_NAME = 'r1';</code></strong>
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |          3 |
+----------------+------------+
2 rows in set (0.00 sec)
</pre><p>
          Now consider a similar table <code class="literal">rc1</code> that uses
          <code class="literal">RANGE COLUMNS</code> partitioning with both
          columns <code class="literal">a</code> and <code class="literal">b</code>
          referenced in the <code class="literal">COLUMNS</code> clause, created
          as shown here:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE rc1 (
    a INT,
    b INT
)
PARTITION BY RANGE COLUMNS(a, b) (
    PARTITION p0 VALUES LESS THAN (5, 12),
    PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE)
);
</pre><p>
          If we insert exactly the same rows into <code class="literal">rc1</code>
          as we just inserted into <code class="literal">r1</code>, the
          distribution of the rows is quite different:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO rc1 VALUES (5,10), (5,11), (5,12);</code></strong>
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME,TABLE_ROWS</code></strong>
    -&gt;     <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE TABLE_NAME = 'rc1';</code></strong>
+--------------+----------------+------------+
| TABLE_SCHEMA | PARTITION_NAME | TABLE_ROWS |
+--------------+----------------+------------+
| p            | p0             |          2 |
| p            | p1             |          1 |
+--------------+----------------+------------+
2 rows in set (0.00 sec)
</pre><p>
          This is because we are comparing rows rather than scalar
          values. We can compare the row values inserted with the
          limiting row value from the <code class="literal">VALUES THAN LESS
          THAN</code> clause used to define partition
          <code class="literal">p0</code> in table <code class="literal">rc1</code>, like
          this:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT (5,10) &lt; (5,12), (5,11) &lt; (5,12), (5,12) &lt; (5,12);</code></strong>
+-----------------+-----------------+-----------------+
| (5,10) &lt; (5,12) | (5,11) &lt; (5,12) | (5,12) &lt; (5,12) |
+-----------------+-----------------+-----------------+
|               1 |               1 |               0 |
+-----------------+-----------------+-----------------+
1 row in set (0.00 sec)
</pre><p>
          The 2 tuples <code class="literal">(5,10)</code> and
          <code class="literal">(5,11)</code> evaluate as less than
          <code class="literal">(5,12)</code>, so they are stored in partition
          <code class="literal">p0</code>. Since 5 is not less than 5 and 12 is
          not less than 12, <code class="literal">(5,12)</code> is considered not
          less than <code class="literal">(5,12)</code>, and is stored in
          partition <code class="literal">p1</code>.
        </p><p>
          The <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> statement in the
          preceding example could also have been written using explicit
          row constructors, like this:
        </p><pre data-lang="sql" class="programlisting">
SELECT ROW(5,10) &lt; ROW(5,12), ROW(5,11) &lt; ROW(5,12), ROW(5,12) &lt; ROW(5,12);
</pre><p>
          For more information about the use of row constructors in
          MySQL, see <a class="xref" href="sql-syntax.html#row-subqueries" title="13.2.11.5 Row Subqueries">Section 13.2.11.5, “Row Subqueries”</a>.
        </p><p>
          For a table partitioned by <code class="literal">RANGE COLUMNS</code>
          using only a single partitioning column, the storing of rows
          in partitions is the same as that of an equivalent table that
          is partitioned by <code class="literal">RANGE</code>. The following
          <code class="literal">CREATE TABLE</code> statement creates a table
          partitioned by <code class="literal">RANGE COLUMNS</code> using 1
          partitioning column:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE rx (
    a INT,
    b INT
)
PARTITION BY RANGE COLUMNS (a)  (
    PARTITION p0 VALUES LESS THAN (5),
    PARTITION p1 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
          If we insert the rows <code class="literal">(5,10)</code>,
          <code class="literal">(5,11)</code>, and <code class="literal">(5,12)</code> into
          this table, we can see that their placement is the same as it
          is for the table <code class="literal">r</code> we created and populated
          earlier:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO rx VALUES (5,10), (5,11), (5,12);</code></strong>
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME,TABLE_ROWS</code></strong>
    -&gt;     <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE TABLE_NAME = 'rx';</code></strong>
+--------------+----------------+------------+
| TABLE_SCHEMA | PARTITION_NAME | TABLE_ROWS |
+--------------+----------------+------------+
| p            | p0             |          0 |
| p            | p1             |          3 |
+--------------+----------------+------------+
2 rows in set (0.00 sec)
</pre><p>
          It is also possible to create tables partitioned by
          <code class="literal">RANGE COLUMNS</code> where limiting values for one
          or more columns are repeated in successive partition
          definitions. You can do this as long as the tuples of column
          values used to define the partitions are strictly increasing.
          For example, each of the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
          TABLE</code></a> statements is valid:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE rc2 (
    a INT,
    b INT
)
PARTITION BY RANGE COLUMNS(a,b) (
    PARTITION p0 VALUES LESS THAN (0,10),
    PARTITION p1 VALUES LESS THAN (10,20),
    PARTITION p2 VALUES LESS THAN (10,30),
    PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
 );

CREATE TABLE rc3 (
    a INT,
    b INT
)
PARTITION BY RANGE COLUMNS(a,b) (
    PARTITION p0 VALUES LESS THAN (0,10),
    PARTITION p1 VALUES LESS THAN (10,20),
    PARTITION p2 VALUES LESS THAN (10,30),
    PARTITION p3 VALUES LESS THAN (10,35),
    PARTITION p4 VALUES LESS THAN (20,40),
    PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)
 );
</pre><p>
          The following statement also succeeds, even though it might
          appear at first glance that it would not, since the limiting
          value of column <code class="literal">b</code> is 25 for partition
          <code class="literal">p0</code> and 20 for partition
          <code class="literal">p1</code>, and the limiting value of column
          <code class="literal">c</code> is 100 for partition
          <code class="literal">p1</code> and 50 for partition
          <code class="literal">p2</code>:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE rc4 (
    a INT,
    b INT,
    c INT
)
PARTITION BY RANGE COLUMNS(a,b,c) (
    PARTITION p0 VALUES LESS THAN (0,25,50),
    PARTITION p1 VALUES LESS THAN (10,20,100),
    PARTITION p2 VALUES LESS THAN (10,30,50)
    PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
 );
</pre><p>
          When designing tables partitioned by <code class="literal">RANGE
          COLUMNS</code>, you can always test successive partition
          definitions by comparing the desired tuples using the
          <a class="link" href="programs.html#mysql" title="4.5.1 mysql — The MySQL Command-Line Tool"><span class="command"><strong>mysql</strong></span></a> client, like this:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT (0,25,50) &lt; (10,20,100), (10,20,100) &lt; (10,30,50);</code></strong>
+-------------------------+--------------------------+
| (0,25,50) &lt; (10,20,100) | (10,20,100) &lt; (10,30,50) |
+-------------------------+--------------------------+
|                       1 |                        1 |
+-------------------------+--------------------------+
1 row in set (0.00 sec)
</pre><p>
          If a <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement
          contains partition definitions that are not in strictly
          increasing order, it fails with an error, as shown in this
          example:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE rcf (</code></strong>
    -&gt;     <strong class="userinput"><code>a INT,</code></strong>
    -&gt;     <strong class="userinput"><code>b INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c INT</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE COLUMNS(a,b,c) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (0,25,50),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (20,20,100),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (10,30,50),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)</code></strong>
    -&gt;  <strong class="userinput"><code>);</code></strong>
<span class="errortext">ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition</span>
</pre><p>
          When you get such an error, you can deduce which partition
          definitions are invalid by making <span class="quote">“<span class="quote">less than</span>”</span>
          comparisons between their column lists. In this case, the
          problem is with the definition of partition
          <code class="literal">p2</code> because the tuple used to define it is
          not less than the tuple used to define partition
          <code class="literal">p3</code>, as shown here:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT (0,25,50) &lt; (20,20,100), (20,20,100) &lt; (10,30,50);</code></strong>
+-------------------------+--------------------------+
| (0,25,50) &lt; (20,20,100) | (20,20,100) &lt; (10,30,50) |
+-------------------------+--------------------------+
|                       1 |                        0 |
+-------------------------+--------------------------+
1 row in set (0.00 sec)
</pre><p>
          It is also possible for <code class="literal">MAXVALUE</code> to appear
          for the same column in more than one <code class="literal">VALUES LESS
          THAN</code> clause when using <code class="literal">RANGE
          COLUMNS</code>. However, the limiting values for individual
          columns in successive partition definitions should otherwise
          be increasing, there should be no more than one partition
          defined where <code class="literal">MAXVALUE</code> is used as the upper
          limit for all column values, and this partition definition
          should appear last in the list of <code class="literal">PARTITION ...
          VALUES LESS THAN</code> clauses. In addition, you cannot
          use <code class="literal">MAXVALUE</code> as the limiting value for the
          first column in more than one partition definition.
        </p><p>
          As stated previously, it is also possible with <code class="literal">RANGE
          COLUMNS</code> partitioning to use non-integer columns as
          partitioning columns. (See
          <a class="xref" href="partitioning.html#partitioning-columns" title="22.2.3 COLUMNS Partitioning">Section 22.2.3, “COLUMNS Partitioning”</a>, for a complete listing
          of these.) Consider a table named <code class="literal">employees</code>
          (which is not partitioned), created using the following
          statement:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
);
</pre><p>
          Using <code class="literal">RANGE COLUMNS</code> partitioning, you can
          create a version of this table that stores each row in one of
          four partitions based on the employee's last name, like
          this:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees_by_lname (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE COLUMNS (lname)  (
    PARTITION p0 VALUES LESS THAN ('g'),
    PARTITION p1 VALUES LESS THAN ('m'),
    PARTITION p2 VALUES LESS THAN ('t'),
    PARTITION p3 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
          Alternatively, you could cause the
          <code class="literal">employees</code> table as created previously to be
          partitioned using this scheme by executing the following
          <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
          TABLE</code></a> statement:
        </p><pre data-lang="sql" class="programlisting">
ALTER TABLE employees PARTITION BY RANGE COLUMNS (lname)  (
    PARTITION p0 VALUES LESS THAN ('g'),
    PARTITION p1 VALUES LESS THAN ('m'),
    PARTITION p2 VALUES LESS THAN ('t'),
    PARTITION p3 VALUES LESS THAN (MAXVALUE)
);
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
            Because different character sets and collations have
            different sort orders, the character sets and collations in
            use may effect which partition of a table partitioned by
            <code class="literal">RANGE COLUMNS</code> a given row is stored in
            when using string columns as partitioning columns. In
            addition, changing the character set or collation for a
            given database, table, or column after such a table is
            created may cause changes in how rows are distributed. For
            example, when using a case-sensitive collation,
            <code class="literal">'and'</code> sorts before
            <code class="literal">'Andersen'</code>, but when using a collation
            that is case insensitive, the reverse is true.
          </p><p>
            For information about how MySQL handles character sets and
            collations, see <a class="xref" href="charset.html" title="Chapter 10 Character Sets, Collations, Unicode">Chapter 10, <i>Character Sets, Collations, Unicode</i></a>.
</p>
</div>
<p>
          Similarly, you can cause the <code class="literal">employees</code>
          table to be partitioned in such a way that each row is stored
          in one of several partitions based on the decade in which the
          corresponding employee was hired using the
          <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
          TABLE</code></a> statement shown here:
        </p><pre data-lang="sql" class="programlisting">
ALTER TABLE employees PARTITION BY RANGE COLUMNS (hired)  (
    PARTITION p0 VALUES LESS THAN ('1970-01-01'),
    PARTITION p1 VALUES LESS THAN ('1980-01-01'),
    PARTITION p2 VALUES LESS THAN ('1990-01-01'),
    PARTITION p3 VALUES LESS THAN ('2000-01-01'),
    PARTITION p4 VALUES LESS THAN ('2010-01-01'),
    PARTITION p5 VALUES LESS THAN (MAXVALUE)
);
</pre><p>
          See <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>, for additional information
          about <code class="literal">PARTITION BY RANGE COLUMNS</code> syntax.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a name="partitioning-columns-list"></a>22.2.3.2 LIST COLUMNS partitioning</h4>

</div>

</div>

</div>
<p>
          MySQL 8.0 provides support for <code class="literal">LIST
          COLUMNS</code> partitioning. This is a variant of
          <code class="literal">LIST</code> partitioning that enables the use of
          multiple columns as partition keys, and for columns of data
          types other than integer types to be used as partitioning
          columns; you can use string types,
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>, and
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> columns. (For more
          information about permitted data types for
          <code class="literal">COLUMNS</code> partitioning columns, see
          <a class="xref" href="partitioning.html#partitioning-columns" title="22.2.3 COLUMNS Partitioning">Section 22.2.3, “COLUMNS Partitioning”</a>.)
        </p><p>
          Suppose that you have a business that has customers in 12
          cities which, for sales and marketing purposes, you organize
          into 4 regions of 3 cities each as shown in the following
          table:
</p>
<div class="informaltable">
<table summary="The example described in the preceding text of a business with four sales and marketing regions, with each region having three cities."><col width="50%"><col width="50%"><thead><tr>
              <th scope="col">Region</th>
              <th scope="col">Cities</th>
            </tr></thead><tbody><tr>
              <td scope="row">1</td>
              <td>Oskarshamn, Högsby, Mönsterås</td>
            </tr><tr>
              <td scope="row">2</td>
              <td>Vimmerby, Hultsfred, Västervik</td>
            </tr><tr>
              <td scope="row">3</td>
              <td>Nässjö, Eksjö, Vetlanda</td>
            </tr><tr>
              <td scope="row">4</td>
              <td>Uppvidinge, Alvesta, Växjo</td>
</tr></tbody></table>
</div>
<p>
          With <code class="literal">LIST COLUMNS</code> partitioning, you can
          create a table for customer data that assigns a row to any of
          4 partitions corresponding to these regions based on the name
          of the city where a customer resides, as shown here:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE customers_1 (
    first_name VARCHAR(25),
    last_name VARCHAR(25),
    street_1 VARCHAR(30),
    street_2 VARCHAR(30),
    city VARCHAR(15),
    renewal DATE
)
PARTITION BY LIST COLUMNS(city) (
    PARTITION pRegion_1 VALUES IN('Oskarshamn', 'Högsby', 'Mönsterås'),
    PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'Västervik'),
    PARTITION pRegion_3 VALUES IN('Nässjö', 'Eksjö', 'Vetlanda'),
    PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'Växjo')
);
</pre><p>
          As with partitioning by <code class="literal">RANGE COLUMNS</code>, you
          do not need to use expressions in the
          <code class="literal">COLUMNS()</code> clause to convert column values
          into integers. (In fact, the use of expressions other than
          column names is not permitted with
          <code class="literal">COLUMNS()</code>.)
        </p><p>
          It is also possible to use <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>
          and <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> columns, as shown
          in the following example that uses the same name and columns
          as the <code class="literal">customers_1</code> table shown previously,
          but employs <code class="literal">LIST COLUMNS</code> partitioning based
          on the <code class="literal">renewal</code> column to store rows in one
          of 4 partitions depending on the week in February 2010 the
          customer's account is scheduled to renew:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE customers_2 (
    first_name VARCHAR(25),
    last_name VARCHAR(25),
    street_1 VARCHAR(30),
    street_2 VARCHAR(30),
    city VARCHAR(15),
    renewal DATE
)
PARTITION BY LIST COLUMNS(renewal) (
    PARTITION pWeek_1 VALUES IN('2010-02-01', '2010-02-02', '2010-02-03',
        '2010-02-04', '2010-02-05', '2010-02-06', '2010-02-07'),
    PARTITION pWeek_2 VALUES IN('2010-02-08', '2010-02-09', '2010-02-10',
        '2010-02-11', '2010-02-12', '2010-02-13', '2010-02-14'),
    PARTITION pWeek_3 VALUES IN('2010-02-15', '2010-02-16', '2010-02-17',
        '2010-02-18', '2010-02-19', '2010-02-20', '2010-02-21'),
    PARTITION pWeek_4 VALUES IN('2010-02-22', '2010-02-23', '2010-02-24',
        '2010-02-25', '2010-02-26', '2010-02-27', '2010-02-28')
);
</pre><p>
          This works, but becomes cumbersome to define and maintain if
          the number of dates involved grows very large; in such cases,
          it is usually more practical to employ
          <code class="literal">RANGE</code> or <code class="literal">RANGE COLUMNS</code>
          partitioning instead. In this case, since the column we wish
          to use as the partitioning key is a
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> column, we use
          <code class="literal">RANGE COLUMNS</code> partitioning, as shown here:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE customers_3 (
    first_name VARCHAR(25),
    last_name VARCHAR(25),
    street_1 VARCHAR(30),
    street_2 VARCHAR(30),
    city VARCHAR(15),
    renewal DATE
)
PARTITION BY RANGE COLUMNS(renewal) (
    PARTITION pWeek_1 VALUES LESS THAN('2010-02-09'),
    PARTITION pWeek_2 VALUES LESS THAN('2010-02-15'),
    PARTITION pWeek_3 VALUES LESS THAN('2010-02-22'),
    PARTITION pWeek_4 VALUES LESS THAN('2010-03-01')
);
</pre><p>
          See <a class="xref" href="partitioning.html#partitioning-columns-range" title="22.2.3.1 RANGE COLUMNS partitioning">Section 22.2.3.1, “RANGE COLUMNS partitioning”</a>, for more
          information.
        </p><p>
          In addition (as with <code class="literal">RANGE COLUMNS</code>
          partitioning), you can use multiple columns in the
          <code class="literal">COLUMNS()</code> clause.
        </p><p>
          See <a class="xref" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax">Section 13.1.18, “CREATE TABLE Syntax”</a>, for additional information
          about <code class="literal">PARTITION BY LIST COLUMNS()</code> syntax.
</p>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-hash"></a>22.2.4 HASH Partitioning</h3>

</div>

</div>

</div>
<div class="toc">
<dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-linear-hash">22.2.4.1 LINEAR HASH Partitioning</a></span></dt></dl>
</div>
<a class="indexterm" name="idm139899483943264"></a><a class="indexterm" name="idm139899483941776"></a><p>
        Partitioning by <code class="literal">HASH</code> is used primarily to
        ensure an even distribution of data among a predetermined number
        of partitions. With range or list partitioning, you must specify
        explicitly which partition a given column value or set of column
        values should be stored in; with hash partitioning, this
        decision is taken care of for you, and you need only specify a
        column value or expression based on a column value to be hashed
        and the number of partitions into which the partitioned table is
        to be divided.
      </p><p>
        To partition a table using <code class="literal">HASH</code> partitioning,
        it is necessary to append to the <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
        TABLE</code></a> statement a <code class="literal">PARTITION BY HASH
        (<em class="replaceable"><code>expr</code></em>)</code> clause, where
        <em class="replaceable"><code>expr</code></em> is an expression that returns an
        integer. This can simply be the name of a column whose type is
        one of MySQL's integer types. In addition, you most likely
        want to follow this with <code class="literal">PARTITIONS
        <em class="replaceable"><code>num</code></em></code>, where
        <em class="replaceable"><code>num</code></em> is a positive integer
        representing the number of partitions into which the table is to
        be divided.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          For simplicity, the tables in the examples that follow do not
          use any keys. You should be aware that, if a table has any
          unique keys, every column used in the partitioning expression
          for this table must be part of every unique key, including the
          primary key. See
          <a class="xref" href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys" title="22.6.1 Partitioning Keys, Primary Keys, and Unique Keys">Section 22.6.1, “Partitioning Keys, Primary Keys, and Unique Keys”</a>,
          for more information.
</p>
</div>
<p>
        The following statement creates a table that uses hashing on the
        <code class="literal">store_id</code> column and is divided into 4
        partitions:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
</pre><p>
        If you do not include a <code class="literal">PARTITIONS</code> clause,
        the number of partitions defaults to <code class="literal">1</code>; using
        the <code class="literal">PARTITIONS</code> keyword without a number
        following it results in a syntax error.
      </p><a class="indexterm" name="idm139899483925776"></a><p>
        You can also use an SQL expression that returns an integer for
        <em class="replaceable"><code>expr</code></em>. For instance, you might want to
        partition based on the year in which an employee was hired. This
        can be done as shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;
</pre><p>
        <em class="replaceable"><code>expr</code></em> must return a nonconstant,
        nonrandom integer value (in other words, it should be varying
        but deterministic), and must not contain any prohibited
        constructs as described in
        <a class="xref" href="partitioning.html#partitioning-limitations" title="22.6 Restrictions and Limitations on Partitioning">Section 22.6, “Restrictions and Limitations on Partitioning”</a>. You should also keep
        in mind that this expression is evaluated each time a row is
        inserted or updated (or possibly deleted); this means that very
        complex expressions may give rise to performance issues,
        particularly when performing operations (such as batch inserts)
        that affect a great many rows at one time.
      </p><p>
        The most efficient hashing function is one which operates upon a
        single table column and whose value increases or decreases
        consistently with the column value, as this allows for
        <span class="quote">“<span class="quote">pruning</span>”</span> on ranges of partitions. That is, the
        more closely that the expression varies with the value of the
        column on which it is based, the more efficiently MySQL can use
        the expression for hash partitioning.
      </p><p>
        For example, where <code class="literal">date_col</code> is a column of
        type <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>, then the expression
        <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS(date_col)</code></a> is said to vary
        directly with the value of <code class="literal">date_col</code>, because
        for every change in the value of <code class="literal">date_col</code>,
        the value of the expression changes in a consistent manner. The
        variance of the expression
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(date_col)</code></a> with respect to
        <code class="literal">date_col</code> is not quite as direct as that of
        <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS(date_col)</code></a>, because not
        every possible change in <code class="literal">date_col</code> produces an
        equivalent change in
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(date_col)</code></a>. Even so,
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(date_col)</code></a> is a good
        candidate for a hashing function, because it varies directly
        with a portion of <code class="literal">date_col</code> and there is no
        possible change in <code class="literal">date_col</code> that produces a
        disproportionate change in
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(date_col)</code></a>.
      </p><p>
        By way of contrast, suppose that you have a column named
        <code class="literal">int_col</code> whose type is
        <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">INT</code></a>. Now consider the expression
        <a class="link" href="functions.html#function_pow"><code class="literal">POW(5-int_col,3) + 6</code></a>. This would
        be a poor choice for a hashing function because a change in the
        value of <code class="literal">int_col</code> is not guaranteed to produce
        a proportional change in the value of the expression. Changing
        the value of <code class="literal">int_col</code> by a given amount can
        produce widely differing changes in the value of the expression.
        For example, changing <code class="literal">int_col</code> from
        <code class="literal">5</code> to <code class="literal">6</code> produces a change
        of <code class="literal">-1</code> in the value of the expression, but
        changing the value of <code class="literal">int_col</code> from
        <code class="literal">6</code> to <code class="literal">7</code> produces a change
        of <code class="literal">-7</code> in the expression value.
      </p><p>
        In other words, the more closely the graph of the column value
        versus the value of the expression follows a straight line as
        traced by the equation
        <code class="literal">y=<em class="replaceable"><code>c</code></em>x</code> where
        <em class="replaceable"><code>c</code></em> is some nonzero constant, the
        better the expression is suited to hashing. This has to do with
        the fact that the more nonlinear an expression is, the more
        uneven the distribution of data among the partitions it tends to
        produce.
      </p><p>
        In theory, pruning is also possible for expressions involving
        more than one column value, but determining which of such
        expressions are suitable can be quite difficult and
        time-consuming. For this reason, the use of hashing expressions
        involving multiple columns is not particularly recommended.
      </p><p>
        When <code class="literal">PARTITION BY HASH</code> is used, the storage
        engine determines which partition of
        <em class="replaceable"><code>num</code></em> partitions to use based on the
        modulus of the result of the expression. In other words, for a
        given expression <em class="replaceable"><code>expr</code></em>, the partition
        in which the record is stored is partition number
        <em class="replaceable"><code>N</code></em>, where
        <code class="literal"><em class="replaceable"><code>N</code></em> =
        MOD(<em class="replaceable"><code>expr</code></em>,
        <em class="replaceable"><code>num</code></em>)</code>. Suppose that table
        <code class="literal">t1</code> is defined as follows, so that it has 4
        partitions:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    PARTITION BY HASH( YEAR(col3) )
    PARTITIONS 4;
</pre><p>
        If you insert a record into <code class="literal">t1</code> whose
        <code class="literal">col3</code> value is
        <code class="literal">'2005-09-15'</code>, then the partition in which it
        is stored is determined as follows:
      </p><pre data-lang="sql" class="programlisting">
MOD(YEAR('2005-09-01'),4)
=  MOD(2005,4)
=  1
</pre><p>
        MySQL 8.0 also supports a variant of
        <code class="literal">HASH</code> partitioning known as
        <span class="firstterm">linear hashing</span> which
        employs a more complex algorithm for determining the placement
        of new rows inserted into the partitioned table. See
        <a class="xref" href="partitioning.html#partitioning-linear-hash" title="22.2.4.1 LINEAR HASH Partitioning">Section 22.2.4.1, “LINEAR HASH Partitioning”</a>, for a description of
        this algorithm.
      </p><p>
        The user-supplied expression is evaluated each time a record is
        inserted or updated. It may also—depending on the
        circumstances—be evaluated when records are deleted.
</p>
<div class="section">

<div class="titlepage">
<div>
<div>
<h4 class="title"><a name="partitioning-linear-hash"></a>22.2.4.1 LINEAR HASH Partitioning</h4>
</div>
</div>
</div>
<a class="indexterm" name="idm139899483875360"></a><a class="indexterm" name="idm139899483873872"></a><p>
          MySQL also supports linear hashing, which differs from regular
          hashing in that linear hashing utilizes a linear powers-of-two
          algorithm whereas regular hashing employs the modulus of the
          hashing function's value.
        </p><p>
          Syntactically, the only difference between linear-hash
          partitioning and regular hashing is the addition of the
          <code class="literal">LINEAR</code> keyword in the <code class="literal">PARTITION
          BY</code> clause, as shown here:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY LINEAR HASH( YEAR(hired) )
PARTITIONS 4;
</pre><p>
          Given an expression <em class="replaceable"><code>expr</code></em>, the
          partition in which the record is stored when linear hashing is
          used is partition number <em class="replaceable"><code>N</code></em> from
          among <em class="replaceable"><code>num</code></em> partitions, where
          <em class="replaceable"><code>N</code></em> is derived according to the
          following algorithm:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
              Find the next power of 2 greater than
              <em class="replaceable"><code>num</code></em>. We call this value
              <em class="replaceable"><code>V</code></em>; it can be calculated as:
            </p><pre data-lang="simple" class="programlisting">
<em class="replaceable"><code>V</code></em> = POWER(2, CEILING(LOG(2, <em class="replaceable"><code>num</code></em>)))
</pre><p>
              (Suppose that <em class="replaceable"><code>num</code></em> is 13. Then
              <a class="link" href="functions.html#function_log"><code class="literal">LOG(2,13)</code></a> is
              3.7004397181411.
              <a class="link" href="functions.html#function_ceiling"><code class="literal">CEILING(3.7004397181411)</code></a> is
              4, and <em class="replaceable"><code>V</code></em> =
              <a class="link" href="functions.html#function_power"><code class="literal">POWER(2,4)</code></a>, which is 16.)
            </p></li><li class="listitem"><p>
              Set <em class="replaceable"><code>N</code></em> =
              <em class="replaceable"><code>F</code></em>(<em class="replaceable"><code>column_list</code></em>)
              &amp; (<em class="replaceable"><code>V</code></em> - 1).
            </p></li><li class="listitem"><p>
              While <em class="replaceable"><code>N</code></em> &gt;=
              <em class="replaceable"><code>num</code></em>:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
                  Set <em class="replaceable"><code>V</code></em> =
                  <em class="replaceable"><code>V</code></em> / 2
                </p></li><li class="listitem"><p>
                  Set <em class="replaceable"><code>N</code></em> =
                  <em class="replaceable"><code>N</code></em> &amp;
                  (<em class="replaceable"><code>V</code></em> - 1)
</p></li></ul>
</div>
</li></ol>
</div>
<p>
          Suppose that the table <code class="literal">t1</code>, using linear
          hash partitioning and having 6 partitions, is created using
          this statement:
        </p><pre data-lang="simple" class="programlisting">
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    PARTITION BY LINEAR HASH( YEAR(col3) )
    PARTITIONS 6;
</pre><p>
          Now assume that you want to insert two records into
          <code class="literal">t1</code> having the <code class="literal">col3</code>
          column values <code class="literal">'2003-04-14'</code> and
          <code class="literal">'1998-10-19'</code>. The partition number for the
          first of these is determined as follows:
        </p><pre data-lang="simple" class="programlisting">
<em class="replaceable"><code>V</code></em> = POWER(2, CEILING( LOG(2,6) )) = 8
<em class="replaceable"><code>N</code></em> = YEAR('2003-04-14') &amp; (8 - 1)
   = 2003 &amp; 7
   = 3

(<span class="emphasis"><em>3 &gt;= 6 is FALSE: record stored in partition #3</em></span>)
</pre><p>
          The number of the partition where the second record is stored
          is calculated as shown here:
        </p><pre data-lang="simple" class="programlisting">
<em class="replaceable"><code>V</code></em> = 8
<em class="replaceable"><code>N</code></em> = YEAR('1998-10-19') &amp; (8 - 1)
  = 1998 &amp; 7
  = 6

(<span class="emphasis"><em>6 &gt;= 6 is TRUE: additional step required</em></span>)

<em class="replaceable"><code>N</code></em> = 6 &amp; ((8 / 2) - 1)
  = 6 &amp; 3
  = 2

(<span class="emphasis"><em>2 &gt;= 6 is FALSE: record stored in partition #2</em></span>)
</pre><p>
          The advantage in partitioning by linear hash is that the
          adding, dropping, merging, and splitting of partitions is made
          much faster, which can be beneficial when dealing with tables
          containing extremely large amounts (terabytes) of data. The
          disadvantage is that data is less likely to be evenly
          distributed between partitions as compared with the
          distribution obtained using regular hash partitioning.
</p>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-key"></a>22.2.5 KEY Partitioning</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483834304"></a><a class="indexterm" name="idm139899483832816"></a><p>
        Partitioning by key is similar to partitioning by hash, except
        that where hash partitioning employs a user-defined expression,
        the hashing function for key partitioning is supplied by the
        MySQL server.
      </p><p>
        The syntax rules for <code class="literal">CREATE TABLE ... PARTITION BY
        KEY</code> are similar to those for creating a table that is
        partitioned by hash. The major differences are listed here:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            <code class="literal">KEY</code> is used rather than
            <code class="literal">HASH</code>.
          </p></li><li class="listitem"><p>
            <code class="literal">KEY</code> takes only a list of zero or more
            column names. Any columns used as the partitioning key must
            comprise part or all of the table's primary key, if the
            table has one. Where no column name is specified as the
            partitioning key, the table's primary key is used, if
            there is one. For example, the following
            <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement is
            valid in MySQL 8.0:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE k1 (
    id INT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;
</pre><p>
            If there is no primary key but there is a unique key, then
            the unique key is used for the partitioning key:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE k1 (
    id INT NOT NULL,
    name VARCHAR(20),
    UNIQUE KEY (id)
)
PARTITION BY KEY()
PARTITIONS 2;
</pre><p>
            However, if the unique key column were not defined as
            <code class="literal">NOT NULL</code>, then the previous statement
            would fail.
          </p><p>
            In both of these cases, the partitioning key is the
            <code class="literal">id</code> column, even though it is not shown in
            the output of <a class="link" href="sql-syntax.html#show-create-table" title="13.7.6.10 SHOW CREATE TABLE Syntax"><code class="literal">SHOW CREATE
            TABLE</code></a> or in the
            <code class="literal">PARTITION_EXPRESSION</code> column of the
            <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
            table.
          </p><p>
            Unlike the case with other partitioning types, columns used
            for partitioning by <code class="literal">KEY</code> are not
            restricted to integer or <code class="literal">NULL</code> values. For
            example, the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
            TABLE</code></a> statement is valid:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tm1 (
    s1 CHAR(32) PRIMARY KEY
)
PARTITION BY KEY(s1)
PARTITIONS 10;
</pre><p>
            The preceding statement would <span class="emphasis"><em>not</em></span> be
            valid, were a different partitioning type to be specified.
            (In this case, simply using <code class="literal">PARTITION BY
            KEY()</code> would also be valid and have the same effect
            as <code class="literal">PARTITION BY KEY(s1)</code>, since
            <code class="literal">s1</code> is the table's primary key.)
          </p><p>
            For additional information about this issue, see
            <a class="xref" href="partitioning.html#partitioning-limitations" title="22.6 Restrictions and Limitations on Partitioning">Section 22.6, “Restrictions and Limitations on Partitioning”</a>.
</p>
<div class="important" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Important
</div>
<p>
              For a key-partitioned table, you cannot execute an
              <code class="literal">ALTER TABLE DROP PRIMARY KEY</code>, as doing
              so generates the error <span class="errortext">ERROR 1466 (HY000):
              Field in list of fields for partition function not found
              in table</span>.
</p>
</div>
</li></ul>
</div>
<a class="indexterm" name="idm139899483805056"></a><a class="indexterm" name="idm139899483803568"></a><p>
        It is also possible to partition a table by linear key. Here is
        a simple example:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tk (
    col1 INT NOT NULL,
    col2 CHAR(5),
    col3 DATE
)
PARTITION BY LINEAR KEY (col1)
PARTITIONS 3;
</pre><p>
        The <code class="literal">LINEAR</code> keyword has the same effect on
        <code class="literal">KEY</code> partitioning as it does on
        <code class="literal">HASH</code> partitioning, with the partition number
        being derived using a powers-of-two algorithm rather than modulo
        arithmetic. See <a class="xref" href="partitioning.html#partitioning-linear-hash" title="22.2.4.1 LINEAR HASH Partitioning">Section 22.2.4.1, “LINEAR HASH Partitioning”</a>, for
        a description of this algorithm and its implications.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-subpartitions"></a>22.2.6 Subpartitioning</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483796144"></a><a class="indexterm" name="idm139899483795072"></a><p>
        Subpartitioning—also known as
        <span class="firstterm">composite
        partitioning</span>—is the further division of each
        partition in a partitioned table. Consider the following
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (id INT, purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) )
    SUBPARTITION BY HASH( TO_DAYS(purchased) )
    SUBPARTITIONS 2 (
        PARTITION p0 VALUES LESS THAN (1990),
        PARTITION p1 VALUES LESS THAN (2000),
        PARTITION p2 VALUES LESS THAN MAXVALUE
    );
</pre><p>
        Table <code class="literal">ts</code> has 3 <code class="literal">RANGE</code>
        partitions. Each of these
        partitions—<code class="literal">p0</code>, <code class="literal">p1</code>,
        and <code class="literal">p2</code>—is further divided into 2
        subpartitions. In effect, the entire table is divided into
        <code class="literal">3 * 2 = 6</code> partitions. However, due to the
        action of the <code class="literal">PARTITION BY RANGE</code> clause, the
        first 2 of these store only those records with a value less than
        1990 in the <code class="literal">purchased</code> column.
      </p><a class="indexterm" name="idm139899483783936"></a><p>
        It is possible to subpartition tables that are partitioned by
        <code class="literal">RANGE</code> or <code class="literal">LIST</code>.
        Subpartitions may use either <code class="literal">HASH</code> or
        <code class="literal">KEY</code> partitioning. This is also known as
        <span class="firstterm">composite partitioning</span>.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          <code class="literal">SUBPARTITION BY HASH</code> and
          <code class="literal">SUBPARTITION BY KEY</code> generally follow the
          same syntax rules as <code class="literal">PARTITION BY HASH</code> and
          <code class="literal">PARTITION BY KEY</code>, respectively. An
          exception to this is that <code class="literal">SUBPARTITION BY
          KEY</code> (unlike <code class="literal">PARTITION BY KEY</code>)
          does not currently support a default column, so the column
          used for this purpose must be specified, even if the table has
          an explicit primary key. This is a known issue which we are
          working to address; see
          <a class="xref" href="partitioning.html#partitioning-limitations-subpartitions" title="Issues with subpartitions">Issues with subpartitions</a>, for
          more information and an example.
</p>
</div>
<a class="indexterm" name="idm139899483772384"></a><p>
        It is also possible to define subpartitions explicitly using
        <code class="literal">SUBPARTITION</code> clauses to specify options for
        individual subpartitions. For example, a more verbose fashion of
        creating the same table <code class="literal">ts</code> as shown in the
        previous example would be:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (id INT, purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) )
    SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
        PARTITION p0 VALUES LESS THAN (1990) (
            SUBPARTITION s0,
            SUBPARTITION s1
        ),
        PARTITION p1 VALUES LESS THAN (2000) (
            SUBPARTITION s2,
            SUBPARTITION s3
        ),
        PARTITION p2 VALUES LESS THAN MAXVALUE (
            SUBPARTITION s4,
            SUBPARTITION s5
        )
    );
</pre><p>
        Some syntactical items of note are listed here:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            Each partition must have the same number of subpartitions.
          </p></li><li class="listitem"><p>
            If you explicitly define any subpartitions using
            <code class="literal">SUBPARTITION</code> on any partition of a
            partitioned table, you must define them all. In other words,
            the following statement will fail:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (id INT, purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) )
    SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
        PARTITION p0 VALUES LESS THAN (1990) (
            SUBPARTITION s0,
            SUBPARTITION s1
        ),
        PARTITION p1 VALUES LESS THAN (2000),
        PARTITION p2 VALUES LESS THAN MAXVALUE (
            SUBPARTITION s2,
            SUBPARTITION s3
        )
    );
</pre><p>
            This statement would still fail even if it used
            <code class="literal">SUBPARTITIONS 2</code>.
          </p></li><li class="listitem"><p>
            Each <code class="literal">SUBPARTITION</code> clause must include (at
            a minimum) a name for the subpartition. Otherwise, you may
            set any desired option for the subpartition or allow it to
            assume its default setting for that option.
          </p></li><li class="listitem"><p>
            Subpartition names must be unique across the entire table.
            For example, the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
            TABLE</code></a> statement is valid:
          </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (id INT, purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) )
    SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
        PARTITION p0 VALUES LESS THAN (1990) (
            SUBPARTITION s0,
            SUBPARTITION s1
        ),
        PARTITION p1 VALUES LESS THAN (2000) (
            SUBPARTITION s2,
            SUBPARTITION s3
        ),
        PARTITION p2 VALUES LESS THAN MAXVALUE (
            SUBPARTITION s4,
            SUBPARTITION s5
        )
    );
</pre></li></ul>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-handling-nulls"></a>22.2.7 How MySQL Partitioning Handles NULL</h3>

</div>

</div>

</div>
<p>
        Partitioning in MySQL does nothing to disallow
        <code class="literal">NULL</code> as the value of a partitioning
        expression, whether it is a column value or the value of a
        user-supplied expression. Even though it is permitted to use
        <code class="literal">NULL</code> as the value of an expression that must
        otherwise yield an integer, it is important to keep in mind that
        <code class="literal">NULL</code> is not a number. MySQL's
        partitioning implementation treats <code class="literal">NULL</code> as
        being less than any non-<code class="literal">NULL</code> value, just as
        <code class="literal">ORDER BY</code> does.
      </p><p>
        This means that treatment of <code class="literal">NULL</code> varies
        between partitioning of different types, and may produce
        behavior which you do not expect if you are not prepared for it.
        This being the case, we discuss in this section how each MySQL
        partitioning type handles <code class="literal">NULL</code> values when
        determining the partition in which a row should be stored, and
        provide examples for each.
      </p><p><b>Handling of NULL with RANGE partitioning. </b>
          If you insert a row into a table partitioned by
          <code class="literal">RANGE</code> such that the column value used to
          determine the partition is <code class="literal">NULL</code>, the row is
          inserted into the lowest partition. Consider these two tables
          in a database named <code class="literal">p</code>, created as follows:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t1 (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE(c1) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (0),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (10),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN MAXVALUE</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.09 sec)

mysql&gt; <strong class="userinput"><code>CREATE TABLE t2 (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE(c1) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (-5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (0),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (10),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN MAXVALUE</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.09 sec)
</pre><p>
        You can see the partitions created by these two
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statements using the
        following query against the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">PARTITIONS</code></a> table in the
        <code class="literal">INFORMATION_SCHEMA</code> database:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH</code></strong>
     &gt;   <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
     &gt;   <strong class="userinput"><code>WHERE TABLE_SCHEMA = 'p' AND TABLE_NAME LIKE 't_';</code></strong>
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| t1         | p0             |          0 |              0 |           0 |
| t1         | p1             |          0 |              0 |           0 |
| t1         | p2             |          0 |              0 |           0 |
| t2         | p0             |          0 |              0 |           0 |
| t2         | p1             |          0 |              0 |           0 |
| t2         | p2             |          0 |              0 |           0 |
| t2         | p3             |          0 |              0 |           0 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.00 sec)
</pre><p>
        (For more information about this table, see
        <a class="xref" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table">Section 24.14, “The INFORMATION_SCHEMA PARTITIONS Table”</a>.) Now let us populate each of
        these tables with a single row containing a
        <code class="literal">NULL</code> in the column used as the partitioning
        key, and verify that the rows were inserted using a pair of
        <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> statements:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO t1 VALUES (NULL, 'mothra');</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO t2 VALUES (NULL, 'mothra');</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM t1;</code></strong>
+------+--------+
| id   | name   |
+------+--------+
| NULL | mothra |
+------+--------+
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM t2;</code></strong>
+------+--------+
| id   | name   |
+------+--------+
| NULL | mothra |
+------+--------+
1 row in set (0.00 sec)
</pre><p>
        You can see which partitions are used to store the inserted rows
        by rerunning the previous query against
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a> and
        inspecting the output:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH</code></strong>
     &gt;   <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
     &gt;   <strong class="userinput"><code>WHERE TABLE_SCHEMA = 'p' AND TABLE_NAME LIKE 't_';</code></strong>
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
<span class="emphasis"><em>| t1         | p0             |          1 |             20 |          20 |</em></span>
| t1         | p1             |          0 |              0 |           0 |
| t1         | p2             |          0 |              0 |           0 |
<span class="emphasis"><em>| t2         | p0             |          1 |             20 |          20 |</em></span>
| t2         | p1             |          0 |              0 |           0 |
| t2         | p2             |          0 |              0 |           0 |
| t2         | p3             |          0 |              0 |           0 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.01 sec)
</pre><p>
        You can also demonstrate that these rows were stored in the
        lowest-numbered partition of each table by dropping these
        partitions, and then re-running the
        <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> statements:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE t1 DROP PARTITION p0;</code></strong>
Query OK, 0 rows affected (0.16 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE t2 DROP PARTITION p0;</code></strong>
Query OK, 0 rows affected (0.16 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM t1;</code></strong>
Empty set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM t2;</code></strong>
Empty set (0.00 sec)
</pre><p>
        (For more information on <code class="literal">ALTER TABLE ... DROP
        PARTITION</code>, see <a class="xref" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax">Section 13.1.8, “ALTER TABLE Syntax”</a>.)
      </p><p>
        <code class="literal">NULL</code> is also treated in this way for
        partitioning expressions that use SQL functions. Suppose that we
        define a table using a <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
        TABLE</code></a> statement such as this one:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tndate (
    id INT,
    dt DATE
)
PARTITION BY RANGE( YEAR(dt) ) (
    PARTITION p0 VALUES LESS THAN (1990),
    PARTITION p1 VALUES LESS THAN (2000),
    PARTITION p2 VALUES LESS THAN MAXVALUE
);
</pre><p>
        As with other MySQL functions,
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(NULL)</code></a> returns
        <code class="literal">NULL</code>. A row with a <code class="literal">dt</code>
        column value of <code class="literal">NULL</code> is treated as though the
        partitioning expression evaluated to a value less than any other
        value, and so is inserted into partition <code class="literal">p0</code>.
      </p><p><b>Handling of NULL with LIST partitioning. </b>
          A table that is partitioned by <code class="literal">LIST</code> admits
          <code class="literal">NULL</code> values if and only if one of its
          partitions is defined using that value-list that contains
          <code class="literal">NULL</code>. The converse of this is that a table
          partitioned by <code class="literal">LIST</code> which does not
          explicitly use <code class="literal">NULL</code> in a value list rejects
          rows resulting in a <code class="literal">NULL</code> value for the
          partitioning expression, as shown in this example:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE ts1 (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LIST(c1) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES IN (0, 3, 6),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES IN (1, 4, 7),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES IN (2, 5, 8)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.01 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO ts1 VALUES (9, 'mothra');</code></strong>
<span class="errortext">ERROR 1504 (HY000): Table has no partition for value 9</span>

mysql&gt; <strong class="userinput"><code>INSERT INTO ts1 VALUES (NULL, 'mothra');</code></strong>
<span class="errortext">ERROR 1504 (HY000): Table has no partition for value NULL</span>
</pre><p>
        Only rows having a <code class="literal">c1</code> value between
        <code class="literal">0</code> and <code class="literal">8</code> inclusive can be
        inserted into <code class="literal">ts1</code>. <code class="literal">NULL</code>
        falls outside this range, just like the number
        <code class="literal">9</code>. We can create tables
        <code class="literal">ts2</code> and <code class="literal">ts3</code> having value
        lists containing <code class="literal">NULL</code>, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE ts2 (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LIST(c1) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES IN (0, 3, 6),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES IN (1, 4, 7),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES IN (2, 5, 8),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES IN (NULL)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.01 sec)

mysql&gt; <strong class="userinput"><code>CREATE TABLE ts3 (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LIST(c1) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES IN (0, 3, 6),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES IN (1, 4, 7, NULL),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES IN (2, 5, 8)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.01 sec)
</pre><p>
        When defining value lists for partitioning, you can (and should)
        treat <code class="literal">NULL</code> just as you would any other value.
        For example, both <code class="literal">VALUES IN (NULL)</code> and
        <code class="literal">VALUES IN (1, 4, 7, NULL)</code> are valid, as are
        <code class="literal">VALUES IN (1, NULL, 4, 7)</code>, <code class="literal">VALUES IN
        (NULL, 1, 4, 7)</code>, and so on. You can insert a row
        having <code class="literal">NULL</code> for column <code class="literal">c1</code>
        into each of the tables <code class="literal">ts2</code> and
        <code class="literal">ts3</code>:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO ts2 VALUES (NULL, 'mothra');</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO ts3 VALUES (NULL, 'mothra');</code></strong>
Query OK, 1 row affected (0.00 sec)
</pre><p>
        By issuing the appropriate query against
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>, you
        can determine which partitions were used to store the rows just
        inserted (we assume, as in the previous examples, that the
        partitioned tables were created in the <code class="literal">p</code>
        database):
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH</code></strong>
     &gt;   <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
     &gt;   <strong class="userinput"><code>WHERE TABLE_SCHEMA = 'p' AND TABLE_NAME LIKE 'ts_';</code></strong>
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| ts2        | p0             |          0 |              0 |           0 |
| ts2        | p1             |          0 |              0 |           0 |
| ts2        | p2             |          0 |              0 |           0 |
<span class="emphasis"><em>| ts2        | p3             |          1 |             20 |          20 |</em></span>
| ts3        | p0             |          0 |              0 |           0 |
<span class="emphasis"><em>| ts3        | p1             |          1 |             20 |          20 |</em></span>
| ts3        | p2             |          0 |              0 |           0 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.01 sec)
</pre><p>
        As shown earlier in this section, you can also verify which
        partitions were used for storing the rows by deleting these
        partitions and then performing a
        <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>.
      </p><p><b>Handling of NULL with HASH and KEY partitioning. </b>
          <code class="literal">NULL</code> is handled somewhat differently for
          tables partitioned by <code class="literal">HASH</code> or
          <code class="literal">KEY</code>. In these cases, any partition
          expression that yields a <code class="literal">NULL</code> value is
          treated as though its return value were zero. We can verify
          this behavior by examining the effects on the file system of
          creating a table partitioned by <code class="literal">HASH</code> and
          populating it with a record containing appropriate values.
          Suppose that you have a table <code class="literal">th</code> (also in
          the <code class="literal">p</code> database) created using the following
          statement:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE th (</code></strong>
    -&gt;     <strong class="userinput"><code>c1 INT,</code></strong>
    -&gt;     <strong class="userinput"><code>c2 VARCHAR(20)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY HASH(c1)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITIONS 2;</code></strong>
Query OK, 0 rows affected (0.00 sec)
</pre><p>
        The partitions belonging to this table can be viewed using the
        query shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; SELECT TABLE_NAME,PARTITION_NAME,TABLE_ROWS,AVG_ROW_LENGTH,DATA_LENGTH
     &gt;   FROM INFORMATION_SCHEMA.PARTITIONS
     &gt;   WHERE TABLE_SCHEMA = 'p' AND TABLE_NAME ='th';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| th         | p0             |          0 |              0 |           0 |
| th         | p1             |          0 |              0 |           0 |
+------------+----------------+------------+----------------+-------------+
2 rows in set (0.00 sec)
</pre><p>
        <code class="literal">TABLE_ROWS</code> for each partition is 0. Now
        insert two rows into <code class="literal">th</code> whose
        <code class="literal">c1</code> column values are <code class="literal">NULL</code>
        and 0, and verify that these rows were inserted, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO th VALUES (NULL, 'mothra'), (0, 'gigan');</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM th;</code></strong>
+------+---------+
| c1   | c2      |
+------+---------+
| NULL | mothra  |
+------+---------+
|    0 | gigan   |
+------+---------+
2 rows in set (0.01 sec)
</pre><p>
        Recall that for any integer <em class="replaceable"><code>N</code></em>, the
        value of <code class="literal">NULL MOD
        <em class="replaceable"><code>N</code></em></code> is always
        <code class="literal">NULL</code>. For tables that are partitioned by
        <code class="literal">HASH</code> or <code class="literal">KEY</code>, this result
        is treated for determining the correct partition as
        <code class="literal">0</code>. Checking the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a> table
        once again, we can see that both rows were inserted into
        partition <code class="literal">p0</code>:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH</code></strong>
     &gt;   <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
     &gt;   <strong class="userinput"><code>WHERE TABLE_SCHEMA = 'p' AND TABLE_NAME ='th';</code></strong>
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
<span class="emphasis"><em>| th         | p0             |          2 |             20 |          20 |</em></span>
| th         | p1             |          0 |              0 |           0 |
+------------+----------------+------------+----------------+-------------+
2 rows in set (0.00 sec)
</pre><p>
        By repeating the last example using <code class="literal">PARTITION BY
        KEY</code> in place of <code class="literal">PARTITION BY HASH</code>
        in the definition of the table, you can verify that
        <code class="literal">NULL</code> is also treated like 0 for this type of
        partitioning.
</p>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-management"></a>22.3 Partition Management</h2>

</div>

</div>

</div>
<div class="toc">
<dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-management-range-list">22.3.1 Management of RANGE and LIST Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-management-hash-key">22.3.2 Management of HASH and KEY Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-management-exchange">22.3.3 Exchanging Partitions and Subpartitions with Tables</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-maintenance">22.3.4 Maintenance of Partitions</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-info">22.3.5 Obtaining Information About Partitions</a></span></dt></dl>
</div>
<a class="indexterm" name="idm139899483599632"></a><a class="indexterm" name="idm139899483598560"></a><a class="indexterm" name="idm139899483597072"></a><a class="indexterm" name="idm139899483595584"></a><a class="indexterm" name="idm139899483594096"></a><a class="indexterm" name="idm139899483592608"></a><p>
      There are a number of ways using SQL statements to modify
      partitioned tables; it is possible to add, drop, redefine, merge,
      or split existing partitions using the partitioning extensions to
      the
      <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
      TABLE</code></a> statement. There are also ways to obtain
      information about partitioned tables and partitions. We discuss
      these topics in the sections that follow.
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          For information about partition management in tables
          partitioned by <code class="literal">RANGE</code> or
          <code class="literal">LIST</code>, see
          <a class="xref" href="partitioning.html#partitioning-management-range-list" title="22.3.1 Management of RANGE and LIST Partitions">Section 22.3.1, “Management of RANGE and LIST Partitions”</a>.
        </p></li><li class="listitem"><p>
          For a discussion of managing <code class="literal">HASH</code> and
          <code class="literal">KEY</code> partitions, see
          <a class="xref" href="partitioning.html#partitioning-management-hash-key" title="22.3.2 Management of HASH and KEY Partitions">Section 22.3.2, “Management of HASH and KEY Partitions”</a>.
        </p></li><li class="listitem"><p>
          See <a class="xref" href="partitioning.html#partitioning-info" title="22.3.5 Obtaining Information About Partitions">Section 22.3.5, “Obtaining Information About Partitions”</a>, for a discussion of
          mechanisms provided in MySQL 8.0 for obtaining
          information about partitioned tables and partitions.
        </p></li><li class="listitem"><p>
          For a discussion of performing maintenance operations on
          partitions, see <a class="xref" href="partitioning.html#partitioning-maintenance" title="22.3.4 Maintenance of Partitions">Section 22.3.4, “Maintenance of Partitions”</a>.
</p></li></ul>
</div>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">
<div class="admon-title">
Note
</div>
<p>
        All partitions of a partitioned table must have the same number
        of subpartitions; it is not possible to change the
        subpartitioning once the table has been created.
</p>
</div>
<p>
      To change a table's partitioning scheme, it is necessary only
      to use the
      <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
      TABLE</code></a> statement with a
      <em class="replaceable"><code>partition_options</code></em> option, which has the
      same syntax as that as used with <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
      TABLE</code></a> for creating a partitioned table; this option
      (also) always begins with the keywords <code class="literal">PARTITION
      BY</code>. Suppose that the following
      <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement was used to
      create a table that is partitioned by range:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE trb3 (id INT, name VARCHAR(50), purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) ) (
        PARTITION p0 VALUES LESS THAN (1990),
        PARTITION p1 VALUES LESS THAN (1995),
        PARTITION p2 VALUES LESS THAN (2000),
        PARTITION p3 VALUES LESS THAN (2005)
    );
</pre><p>
      To repartition this table so that it is partitioned by key into
      two partitions using the <code class="literal">id</code> column value as the
      basis for the key, you can use this statement:
    </p><pre data-lang="sql" class="programlisting">
ALTER TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2;
</pre><p>
      This has the same effect on the structure of the table as dropping
      the table and re-creating it using <code class="literal">CREATE TABLE trb3
      PARTITION BY KEY(id) PARTITIONS 2;</code>.
    </p><p>
      <code class="literal">ALTER TABLE ... ENGINE = ...</code> changes only the
      storage engine used by the table, and leaves the table's
      partitioning scheme intact. The statement succeeds only if the
      target storage engine provides partitioning support. You can use
      <code class="literal">ALTER TABLE ... REMOVE PARTITIONING</code> to remove a
      table's partitioning; see <a class="xref" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax">Section 13.1.8, “ALTER TABLE Syntax”</a>.
</p>
<div class="important" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Important
</div>
<p>
        Only a single <code class="literal">PARTITION BY</code>, <code class="literal">ADD
        PARTITION</code>, <code class="literal">DROP PARTITION</code>,
        <code class="literal">REORGANIZE PARTITION</code>, or <code class="literal">COALESCE
        PARTITION</code> clause can be used in a given
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement. If you (for example) wish to drop a
        partition and reorganize a table's remaining partitions,
        you must do so in two separate
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statements (one using <code class="literal">DROP
        PARTITION</code> and then a second one using
        <code class="literal">REORGANIZE PARTITION</code>).
</p>
</div>
<p>
      You can delete all rows from one or more selected partitions using
      <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
      TRUNCATE PARTITION</code></a>.
</p>
<div class="section">

<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-management-range-list"></a>22.3.1 Management of RANGE and LIST Partitions</h3>
</div>
</div>
</div>
<a class="indexterm" name="idm139899483553904"></a><a class="indexterm" name="idm139899483552416"></a><a class="indexterm" name="idm139899483550928"></a><a class="indexterm" name="idm139899483549440"></a><p>
        Adding and dropping of range and list partitions are handled in
        a similar fashion, so we discuss the management of both sorts of
        partitioning in this section. For information about working with
        tables that are partitioned by hash or key, see
        <a class="xref" href="partitioning.html#partitioning-management-hash-key" title="22.3.2 Management of HASH and KEY Partitions">Section 22.3.2, “Management of HASH and KEY Partitions”</a>.
      </p><p>
        Dropping a partition from a table that is partitioned by either
        <code class="literal">RANGE</code> or by <code class="literal">LIST</code> can be
        accomplished using the
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement with the <code class="literal">DROP
        PARTITION</code> option. Suppose that you have created a
        table that is partitioned by range and then populated with 10
        records using the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
        TABLE</code></a> and <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a>
        statements:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE tr (id INT, name VARCHAR(50), purchased DATE)</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION BY RANGE( YEAR(purchased) ) (</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (1990),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (1995),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (2000),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (2005),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p4 VALUES LESS THAN (2010),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p5 VALUES LESS THAN (2015)</code></strong>
    -&gt;     <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.28 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO tr VALUES</code></strong>
    -&gt;     <strong class="userinput"><code>(1, 'desk organiser', '2003-10-15'),</code></strong>
    -&gt;     <strong class="userinput"><code>(2, 'alarm clock', '1997-11-05'),</code></strong>
    -&gt;     <strong class="userinput"><code>(3, 'chair', '2009-03-10'),</code></strong>
    -&gt;     <strong class="userinput"><code>(4, 'bookcase', '1989-01-10'),</code></strong>
    -&gt;     <strong class="userinput"><code>(5, 'exercise bike', '2014-05-09'),</code></strong>
    -&gt;     <strong class="userinput"><code>(6, 'sofa', '1987-06-05'),</code></strong>
    -&gt;     <strong class="userinput"><code>(7, 'espresso maker', '2011-11-22'),</code></strong>
    -&gt;     <strong class="userinput"><code>(8, 'aquarium', '1992-08-04'),</code></strong>
    -&gt;     <strong class="userinput"><code>(9, 'study desk', '2006-09-16'),</code></strong>
    -&gt;     <strong class="userinput"><code>(10, 'lava lamp', '1998-12-25');</code></strong>
Query OK, 10 rows affected (0.05 sec)
Records: 10  Duplicates: 0  Warnings: 0
</pre><p>
        You can see which items should have been inserted into partition
        <code class="literal">p2</code> as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM tr</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';</code></strong>
+------+-------------+------------+
| id   | name        | purchased  |
+------+-------------+------------+
|    2 | alarm clock | 1997-11-05 |
|   10 | lava lamp   | 1998-12-25 |
+------+-------------+------------+
2 rows in set (0.00 sec)
</pre><p>
        You can also get this information using partition selection, as
        shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM tr PARTITION (p2);</code></strong>
+------+-------------+------------+
| id   | name        | purchased  |
+------+-------------+------------+
|    2 | alarm clock | 1997-11-05 |
|   10 | lava lamp   | 1998-12-25 |
+------+-------------+------------+
2 rows in set (0.00 sec)
</pre><p>
        See <a class="xref" href="partitioning.html#partitioning-selection" title="22.5 Partition Selection">Section 22.5, “Partition Selection”</a>, for more
        information.
      </p><p>
        To drop the partition named <code class="literal">p2</code>, execute the
        following command:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE tr DROP PARTITION p2;</code></strong>
Query OK, 0 rows affected (0.03 sec)
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          The <a class="ulink" href="http://dev.mysql.com/doc/refman/5.7/en/mysql-cluster.html" target="_top"><code class="literal">NDBCLUSTER</code></a> storage engine
          does not support <code class="literal">ALTER TABLE ... DROP
          PARTITION</code>. It does, however, support the other
          partitioning-related extensions to
          <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
          TABLE</code></a> that are described in this chapter.
</p>
</div>
<p>
        It is very important to remember that, <span class="emphasis"><em>when you drop a
        partition, you also delete all the data that was stored in that
        partition</em></span>. You can see that this is the case by
        re-running the previous <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>
        query:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM tr WHERE purchased</code></strong>
    -&gt; <strong class="userinput"><code>BETWEEN '1995-01-01' AND '1999-12-31';</code></strong>
Empty set (0.00 sec)
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          <code class="literal">DROP PARTITION</code> is supported by native
          partitioning in-place APIs and may be used with
          <code class="literal">ALGORITHM={COPY|INPLACE}</code>. <code class="literal">DROP
          PARTITION</code> with <code class="literal">ALGORITHM=INPLACE</code>
          deletes data stored in the partition and drops the partition.
          However, <code class="literal">DROP PARTITION</code> with
          <code class="literal">ALGORITHM=COPY</code> or
          <a class="link" href="server-administration.html#sysvar_old_alter_table"><code class="literal">old_alter_table=ON</code></a> rebuilds
          the partitioned table and attempts to move data from the
          dropped partition to another partition with a compatible
          <code class="literal">PARTITION ... VALUES</code> definition. Data that
          cannot be moved to another partition is deleted.
</p>
</div>
<p>
        Because of this, you must have the
        <a class="link" href="security.html#priv_drop"><code class="literal">DROP</code></a> privilege for a table before
        you can execute <code class="literal">ALTER TABLE ... DROP
        PARTITION</code> on that table.
      </p><p>
        If you wish to drop all data from all partitions while
        preserving the table definition and its partitioning scheme, use
        the <a class="link" href="sql-syntax.html#truncate-table" title="13.1.34 TRUNCATE TABLE Syntax"><code class="literal">TRUNCATE TABLE</code></a> statement.
        (See <a class="xref" href="sql-syntax.html#truncate-table" title="13.1.34 TRUNCATE TABLE Syntax">Section 13.1.34, “TRUNCATE TABLE Syntax”</a>.)
      </p><p>
        If you intend to change the partitioning of a table
        <span class="emphasis"><em>without</em></span> losing data, use <code class="literal">ALTER
        TABLE ... REORGANIZE PARTITION</code> instead. See below or
        in <a class="xref" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax">Section 13.1.8, “ALTER TABLE Syntax”</a>, for information about
        <code class="literal">REORGANIZE PARTITION</code>.
      </p><p>
        If you now execute a <a class="link" href="sql-syntax.html#show-create-table" title="13.7.6.10 SHOW CREATE TABLE Syntax"><code class="literal">SHOW CREATE
        TABLE</code></a> statement, you can see how the partitioning
        makeup of the table has been changed:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE tr\G</code></strong>
*************************** 1. row ***************************
       Table: tr
Create Table: CREATE TABLE `tr` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE ( YEAR(purchased))
(PARTITION p0 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1995) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2005) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (2010) ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN (2015) ENGINE = InnoDB) */
1 row in set (0.00 sec)
</pre><p>
        When you insert new rows into the changed table with
        <code class="literal">purchased</code> column values between
        <code class="literal">'1995-01-01'</code> and
        <code class="literal">'2004-12-31'</code> inclusive, those rows will be
        stored in partition <code class="literal">p3</code>. You can verify this
        as follows:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO tr VALUES (11, 'pencil holder', '1995-07-12');</code></strong>
Query OK, 1 row affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM tr WHERE purchased</code></strong>
    -&gt; <strong class="userinput"><code>BETWEEN '1995-01-01' AND '2004-12-31';</code></strong>
+------+----------------+------------+
| id   | name           | purchased  |
+------+----------------+------------+
|    1 | desk organiser | 2003-10-15 |
|   11 | pencil holder  | 1995-07-12 |
+------+----------------+------------+
2 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE tr DROP PARTITION p3;</code></strong>
Query OK, 0 rows affected (0.03 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM tr WHERE purchased</code></strong>
    -&gt; <strong class="userinput"><code>BETWEEN '1995-01-01' AND '2004-12-31';</code></strong>
Empty set (0.00 sec)
</pre><p>
        The number of rows dropped from the table as a result of
        <code class="literal">ALTER TABLE ... DROP PARTITION</code> is not
        reported by the server as it would be by the equivalent
        <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a> query.
      </p><p>
        Dropping <code class="literal">LIST</code> partitions uses exactly the
        same <code class="literal">ALTER TABLE ... DROP PARTITION</code> syntax as
        used for dropping <code class="literal">RANGE</code> partitions. However,
        there is one important difference in the effect this has on your
        use of the table afterward: You can no longer insert into the
        table any rows having any of the values that were included in
        the value list defining the deleted partition. (See
        <a class="xref" href="partitioning.html#partitioning-list" title="22.2.2 LIST Partitioning">Section 22.2.2, “LIST Partitioning”</a>, for an example.)
      </p><p>
        To add a new range or list partition to a previously partitioned
        table, use the <code class="literal">ALTER TABLE ... ADD PARTITION</code>
        statement. For tables which are partitioned by
        <code class="literal">RANGE</code>, this can be used to add a new range to
        the end of the list of existing partitions. Suppose that you
        have a partitioned table containing membership data for your
        organization, which is defined as follows:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE members (
    id INT,
    fname VARCHAR(25),
    lname VARCHAR(25),
    dob DATE
)
PARTITION BY RANGE( YEAR(dob) ) (
    PARTITION p0 VALUES LESS THAN (1980),
    PARTITION p1 VALUES LESS THAN (1990),
    PARTITION p2 VALUES LESS THAN (2000)
);
</pre><p>
        Suppose further that the minimum age for members is 16. As the
        calendar approaches the end of 2015, you realize that you will
        soon be admitting members who were born in 2000 (and later). You
        can modify the <code class="literal">members</code> table to accommodate
        new members born in the years 2000 to 2010 as shown here:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));
</pre><p>
        With tables that are partitioned by range, you can use
        <code class="literal">ADD PARTITION</code> to add new partitions to the
        high end of the partitions list only. Trying to add a new
        partition in this manner between or before existing partitions
        results in an error as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE members</code></strong>
     &gt;     <strong class="userinput"><code>ADD PARTITION (</code></strong>
     &gt;     <strong class="userinput"><code>PARTITION n VALUES LESS THAN (1970));</code></strong>
ERROR 1463 (HY000): VALUES LESS THAN value must be strictly »
   increasing for each partition
</pre><p>
        You can work around this problem by reorganizing the first
        partition into two new ones that split the range between them,
        like this:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members
    REORGANIZE PARTITION p0 INTO (
        PARTITION n0 VALUES LESS THAN (1970),
        PARTITION n1 VALUES LESS THAN (1980)
);
</pre><p>
        Using <a class="link" href="sql-syntax.html#show-create-table" title="13.7.6.10 SHOW CREATE TABLE Syntax"><code class="literal">SHOW CREATE TABLE</code></a> you can
        see that the <code class="literal">ALTER TABLE</code> statement has had
        the desired effect:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE members\G</code></strong>
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `fname` varchar(25) DEFAULT NULL,
  `lname` varchar(25) DEFAULT NULL,
  `dob` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE ( YEAR(dob))
(PARTITION n0 VALUES LESS THAN (1970) ENGINE = InnoDB,
 PARTITION n1 VALUES LESS THAN (1980) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2010) ENGINE = InnoDB) */
1 row in set (0.00 sec)
</pre><p>
        See also <a class="xref" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations">Section 13.1.8.1, “ALTER TABLE Partition Operations”</a>.
      </p><p>
        You can also use <code class="literal">ALTER TABLE ... ADD
        PARTITION</code> to add new partitions to a table that is
        partitioned by <code class="literal">LIST</code>. Suppose a table
        <code class="literal">tt</code> is defined using the following
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tt (
    id INT,
    data INT
)
PARTITION BY LIST(data) (
    PARTITION p0 VALUES IN (5, 10, 15),
    PARTITION p1 VALUES IN (6, 12, 18)
);
</pre><p>
        You can add a new partition in which to store rows having the
        <code class="literal">data</code> column values <code class="literal">7</code>,
        <code class="literal">14</code>, and <code class="literal">21</code> as shown:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE tt ADD PARTITION (PARTITION p2 VALUES IN (7, 14, 21));
</pre><p>
        Keep in mind that you <span class="emphasis"><em>cannot</em></span> add a new
        <code class="literal">LIST</code> partition encompassing any values that
        are already included in the value list of an existing partition.
        If you attempt to do so, an error will result:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE tt ADD PARTITION </code></strong>
     &gt;     <strong class="userinput"><code>(PARTITION np VALUES IN (4, 8, 12));</code></strong>
ERROR 1465 (HY000): Multiple definition of same constant »
                    in list partitioning
</pre><p>
        Because any rows with the <code class="literal">data</code> column value
        <code class="literal">12</code> have already been assigned to partition
        <code class="literal">p1</code>, you cannot create a new partition on
        table <code class="literal">tt</code> that includes <code class="literal">12</code>
        in its value list. To accomplish this, you could drop
        <code class="literal">p1</code>, and add <code class="literal">np</code> and then a
        new <code class="literal">p1</code> with a modified definition. However,
        as discussed earlier, this would result in the loss of all data
        stored in <code class="literal">p1</code>—and it is often the case
        that this is not what you really want to do. Another solution
        might appear to be to make a copy of the table with the new
        partitioning and to copy the data into it using
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE ...
        SELECT ...</code></a>, then drop the old table and rename the new
        one, but this could be very time-consuming when dealing with a
        large amounts of data. This also might not be feasible in
        situations where high availability is a requirement.
      </p><p>
        You can add multiple partitions in a single <code class="literal">ALTER TABLE
        ... ADD PARTITION</code> statement as shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE employees (
  id INT NOT NULL,
  fname VARCHAR(50) NOT NULL,
  lname VARCHAR(50) NOT NULL,
  hired DATE NOT NULL
)
PARTITION BY RANGE( YEAR(hired) ) (
  PARTITION p1 VALUES LESS THAN (1991),
  PARTITION p2 VALUES LESS THAN (1996),
  PARTITION p3 VALUES LESS THAN (2001),
  PARTITION p4 VALUES LESS THAN (2005)
);

ALTER TABLE employees ADD PARTITION (
    PARTITION p5 VALUES LESS THAN (2010),
    PARTITION p6 VALUES LESS THAN MAXVALUE
);
</pre><p>
        Fortunately, MySQL's partitioning implementation provides ways
        to redefine partitions without losing data. Let us look first at
        a couple of simple examples involving <code class="literal">RANGE</code>
        partitioning. Recall the <code class="literal">members</code> table which
        is now defined as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE members\G</code></strong>
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `fname` varchar(25) DEFAULT NULL,
  `lname` varchar(25) DEFAULT NULL,
  `dob` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE ( YEAR(dob))
(PARTITION n0 VALUES LESS THAN (1970) ENGINE = InnoDB,
 PARTITION n1 VALUES LESS THAN (1980) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2010) ENGINE = InnoDB) */
1 row in set (0.00 sec)
</pre><p>
        Suppose that you would like to move all rows representing
        members born before 1960 into a separate partition. As we have
        already seen, this cannot be done using
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... ADD PARTITION</code></a>. However, you can use another
        partition-related extension to
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> to accomplish this:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members REORGANIZE PARTITION n0 INTO (
    PARTITION s0 VALUES LESS THAN (1960),
    PARTITION s1 VALUES LESS THAN (1970)
);
</pre><p>
        In effect, this command splits partition <code class="literal">p0</code>
        into two new partitions <code class="literal">s0</code> and
        <code class="literal">s1</code>. It also moves the data that was stored in
        <code class="literal">p0</code> into the new partitions according to the
        rules embodied in the two <code class="literal">PARTITION ... VALUES
        ...</code> clauses, so that <code class="literal">s0</code> contains
        only those records for which
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(dob)</code></a> is less than 1960 and
        <code class="literal">s1</code> contains those rows in which
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR(dob)</code></a> is greater than or
        equal to 1960 but less than 1970.
      </p><p>
        A <code class="literal">REORGANIZE PARTITION</code> clause may also be
        used for merging adjacent partitions. You can reverse the effect
        of the previous statement on the <code class="literal">members</code>
        table as shown here:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members REORGANIZE PARTITION s0,s1 INTO (
    PARTITION p0 VALUES LESS THAN (1970)
);
</pre><p>
        No data is lost in splitting or merging partitions using
        <code class="literal">REORGANIZE PARTITION</code>. In executing the above
        statement, MySQL moves all of the records that were stored in
        partitions <code class="literal">s0</code> and <code class="literal">s1</code> into
        partition <code class="literal">p0</code>.
      </p><p>
        The general syntax for <code class="literal">REORGANIZE PARTITION</code>
        is shown here:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE <em class="replaceable"><code>tbl_name</code></em>
    REORGANIZE PARTITION <em class="replaceable"><code>partition_list</code></em>
    INTO (<em class="replaceable"><code>partition_definitions</code></em>);
</pre><p>
        Here, <em class="replaceable"><code>tbl_name</code></em> is the name of the
        partitioned table, and <em class="replaceable"><code>partition_list</code></em>
        is a comma-separated list of names of one or more existing
        partitions to be changed.
        <em class="replaceable"><code>partition_definitions</code></em> is a
        comma-separated list of new partition definitions, which follow
        the same rules as for the
        <em class="replaceable"><code>partition_definitions</code></em> list used in
        <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a>. You are not limited
        to merging several partitions into one, or to splitting one
        partition into many, when using <code class="literal">REORGANIZE
        PARTITION</code>. For example, you can reorganize all four
        partitions of the <code class="literal">members</code> table into two,
        like this:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members REORGANIZE PARTITION p0,p1,p2,p3 INTO (
    PARTITION m0 VALUES LESS THAN (1980),
    PARTITION m1 VALUES LESS THAN (2000)
);
</pre><p>
        You can also use <code class="literal">REORGANIZE PARTITION</code> with
        tables that are partitioned by <code class="literal">LIST</code>. Let us
        return to the problem of adding a new partition to the
        list-partitioned <code class="literal">tt</code> table and failing because
        the new partition had a value that was already present in the
        value-list of one of the existing partitions. We can handle this
        by adding a partition that contains only nonconflicting values,
        and then reorganizing the new partition and the existing one so
        that the value which was stored in the existing one is now moved
        to the new one:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8));
ALTER TABLE tt REORGANIZE PARTITION p1,np INTO (
    PARTITION p1 VALUES IN (6, 18),
    PARTITION np VALUES in (4, 8, 12)
);
</pre><p>
        Here are some key points to keep in mind when using
        <code class="literal">ALTER TABLE ... REORGANIZE PARTITION</code> to
        repartition tables that are partitioned by
        <code class="literal">RANGE</code> or <code class="literal">LIST</code>:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            The <code class="literal">PARTITION</code> options used to determine
            the new partitioning scheme are subject to the same rules as
            those used with a <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
            TABLE</code></a> statement.
          </p><p>
            A new <code class="literal">RANGE</code> partitioning scheme cannot
            have any overlapping ranges; a new <code class="literal">LIST</code>
            partitioning scheme cannot have any overlapping sets of
            values.
          </p></li><li class="listitem"><p>
            The combination of partitions in the
            <em class="replaceable"><code>partition_definitions</code></em> list should
            account for the same range or set of values overall as the
            combined partitions named in the
            <em class="replaceable"><code>partition_list</code></em>.
          </p><p>
            For example, partitions <code class="literal">p1</code> and
            <code class="literal">p2</code> together cover the years 1980 through
            1999 in the <code class="literal">members</code> table used as an
            example in this section. Any reorganization of these two
            partitions should cover the same range of years overall.
          </p></li><li class="listitem"><p>
            For tables partitioned by <code class="literal">RANGE</code>, you can
            reorganize only adjacent partitions; you cannot skip over
            range partitions.
          </p><p>
            For instance, you could not reorganize the example
            <code class="literal">members</code> table using a statement beginning
            with <code class="literal">ALTER TABLE members REORGANIZE PARTITION p0,p2
            INTO ...</code> because <code class="literal">p0</code> covers the
            years prior to 1970 and <code class="literal">p2</code> the years from
            1990 through 1999 inclusive, so these are not adjacent
            partitions. (You cannot skip partition <code class="literal">p1</code>
            in this case.)
          </p></li><li class="listitem"><p>
            You cannot use <code class="literal">REORGANIZE PARTITION</code> to
            change the type of partitioning used by the table; for
            example, you cannot change <code class="literal">RANGE</code>
            partitions to <code class="literal">HASH</code> partitions or the
            reverse. You also cannot use this statement to change the
            partitioning expression or column. To accomplish either of
            these tasks without dropping and re-creating the table, you
            can use
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            TABLE ... PARTITION BY ...</code></a>, as shown here:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE members
    PARTITION BY HASH( YEAR(dob) )
    PARTITIONS 8;
</pre></li></ul>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-management-hash-key"></a>22.3.2 Management of HASH and KEY Partitions</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483364736"></a><a class="indexterm" name="idm139899483363248"></a><a class="indexterm" name="idm139899483361760"></a><a class="indexterm" name="idm139899483360272"></a><p>
        Tables which are partitioned by hash or by key are very similar
        to one another with regard to making changes in a partitioning
        setup, and both differ in a number of ways from tables which
        have been partitioned by range or list. For that reason, this
        section addresses the modification of tables partitioned by hash
        or by key only. For a discussion of adding and dropping of
        partitions of tables that are partitioned by range or list, see
        <a class="xref" href="partitioning.html#partitioning-management-range-list" title="22.3.1 Management of RANGE and LIST Partitions">Section 22.3.1, “Management of RANGE and LIST Partitions”</a>.
      </p><p>
        You cannot drop partitions from tables that are partitioned by
        <code class="literal">HASH</code> or <code class="literal">KEY</code> in the same
        way that you can from tables that are partitioned by
        <code class="literal">RANGE</code> or <code class="literal">LIST</code>. However,
        you can merge <code class="literal">HASH</code> or <code class="literal">KEY</code>
        partitions using <code class="literal">ALTER TABLE ... COALESCE
        PARTITION</code>. Suppose that a <code class="literal">clients</code>
        table containing data about clients is divided into 12
        partitions, created as shown here:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE clients (
    id INT,
    fname VARCHAR(30),
    lname VARCHAR(30),
    signed DATE
)
PARTITION BY HASH( MONTH(signed) )
PARTITIONS 12;
</pre><p>
        To reduce the number of partitions from 12 to 8, execute the
        following
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE clients COALESCE PARTITION 4;</code></strong>
Query OK, 0 rows affected (0.02 sec)
</pre><p>
        <code class="literal">COALESCE</code> works equally well with tables that
        are partitioned by <code class="literal">HASH</code>,
        <code class="literal">KEY</code>, <code class="literal">LINEAR HASH</code>, or
        <code class="literal">LINEAR KEY</code>. Here is an example similar to the
        previous one, differing only in that the table is partitioned by
        <code class="literal">LINEAR KEY</code>:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE clients_lk (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT,</code></strong>
    -&gt;     <strong class="userinput"><code>fname VARCHAR(30),</code></strong>
    -&gt;     <strong class="userinput"><code>lname VARCHAR(30),</code></strong>
    -&gt;     <strong class="userinput"><code>signed DATE</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY LINEAR KEY(signed)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITIONS 12;</code></strong>
Query OK, 0 rows affected (0.03 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE clients_lk COALESCE PARTITION 4;</code></strong>
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0
</pre><p>
        The number following <code class="literal">COALESCE PARTITION</code> is
        the number of partitions to merge into the remainder—in
        other words, it is the number of partitions to remove from the
        table.
      </p><p>
        Attempting to remove more partitions than are in the table
        results in an error like this one:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE clients COALESCE PARTITION 18;</code></strong>
ERROR 1478 (HY000): Cannot remove all partitions, use DROP TABLE instead
</pre><p>
        To increase the number of partitions for the
        <code class="literal">clients</code> table from 12 to 18, use
        <code class="literal">ALTER TABLE ... ADD PARTITION</code> as shown here:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE clients ADD PARTITION PARTITIONS 6;
</pre>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-management-exchange"></a>22.3.3 Exchanging Partitions and Subpartitions with Tables</h3>

</div>

</div>

</div>
<p>
        In MySQL 8.0, it is possible to exchange a table
        partition or subpartition with a table using <code class="literal">ALTER
        TABLE <em class="replaceable"><code>pt</code></em> EXCHANGE PARTITION
        <em class="replaceable"><code>p</code></em> WITH TABLE
        <em class="replaceable"><code>nt</code></em></code>, where
        <em class="replaceable"><code>pt</code></em> is the partitioned table and
        <em class="replaceable"><code>p</code></em> is the partition or subpartition of
        <em class="replaceable"><code>pt</code></em> to be exchanged with unpartitioned
        table <em class="replaceable"><code>nt</code></em>, provided that the following
        statements are true:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
            Table <em class="replaceable"><code>nt</code></em> is not itself
            partitioned.
          </p></li><li class="listitem"><p>
            Table <em class="replaceable"><code>nt</code></em> is not a temporary
            table.
          </p></li><li class="listitem"><p>
            The structures of tables <em class="replaceable"><code>pt</code></em> and
            <em class="replaceable"><code>nt</code></em> are otherwise identical.
          </p></li><li class="listitem"><p>
            Table <code class="literal">nt</code> contains no foreign key
            references, and no other table has any foreign keys that
            refer to <code class="literal">nt</code>.
          </p></li><li class="listitem"><p>
            There are no rows in <em class="replaceable"><code>nt</code></em> that lie
            outside the boundaries of the partition definition for
            <em class="replaceable"><code>p</code></em>. This condition does not apply
            if <code class="literal">WITHOUT VALIDATION</code> is used.
</p></li></ol>
</div>
<p>
        In addition to the <a class="link" href="security.html#priv_alter"><code class="literal">ALTER</code></a>,
        <a class="link" href="security.html#priv_insert"><code class="literal">INSERT</code></a>, and
        <a class="link" href="security.html#priv_create"><code class="literal">CREATE</code></a> privileges usually
        required for <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE</code></a>
        statements, you must have the
        <a class="link" href="security.html#priv_drop"><code class="literal">DROP</code></a> privilege to perform
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a>.
      </p><p>
        You should also be aware of the following effects of
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a>:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            Executing <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER
            TABLE ... EXCHANGE PARTITION</code></a> does not invoke any
            triggers on either the partitioned table or the table to be
            exchanged.
          </p></li><li class="listitem"><p>
            Any <code class="literal">AUTO_INCREMENT</code> columns in the
            exchanged table are reset.
          </p></li><li class="listitem"><p>
            The <code class="literal">IGNORE</code> keyword has no effect when
            used with <code class="literal">ALTER TABLE ... EXCHANGE
            PARTITION</code>.
</p></li></ul>
</div>
<p>
        The syntax for
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a> is shown here, where
        <em class="replaceable"><code>pt</code></em> is the partitioned table,
        <em class="replaceable"><code>p</code></em> is the partition (or subpartition)
        to be exchanged, and <em class="replaceable"><code>nt</code></em> is the
        nonpartitioned table to be exchanged with
        <em class="replaceable"><code>p</code></em>:
      </p><pre data-lang="sql" class="programlisting">
ALTER TABLE <em class="replaceable"><code>pt</code></em>
    EXCHANGE PARTITION <em class="replaceable"><code>p</code></em>
    WITH TABLE <em class="replaceable"><code>nt</code></em>;
</pre><p>
        Optionally, you can append <code class="literal">WITH VALIDATION</code> or
        <code class="literal">WITHOUT VALIDATION</code>. When <code class="literal">WITHOUT
        VALIDATION</code> is specified, the
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a> operation does not perform any
        row-by-row validation when exchanging a partition a
        nonpartitioned table, allowing database administrators to assume
        responsibility for ensuring that rows are within the boundaries
        of the partition definition. <code class="literal">WITH VALIDATION</code>
        is the default.
      </p><p>
        One and only one partition or subpartition may be exchanged with
        one and only one nonpartitioned table in a single
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE
        EXCHANGE PARTITION</code></a> statement. To exchange multiple
        partitions or subpartitions, use multiple
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE
        EXCHANGE PARTITION</code></a> statements. <code class="literal">EXCHANGE
        PARTITION</code> may not be combined with other
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE</code></a> options. The
        partitioning and (if applicable) subpartitioning used by the
        partitioned table may be of any type or types supported in MySQL
        8.0.
</p>
<h4><a name="idm139899483280128"></a>Exchanging a Partition with a Nonpartitioned Table</h4>
<p>
        Suppose that a partitioned table <code class="literal">e</code> has been
        created and populated using the following SQL statements:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE e (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30)
)
    PARTITION BY RANGE (id) (
        PARTITION p0 VALUES LESS THAN (50),
        PARTITION p1 VALUES LESS THAN (100),
        PARTITION p2 VALUES LESS THAN (150),
        PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO e VALUES
    (1669, "Jim", "Smith"),
    (337, "Mary", "Jones"),
    (16, "Frank", "White"),
    (2005, "Linda", "Black");
</pre><p>
        Now we create a nonpartitioned copy of <code class="literal">e</code>
        named <code class="literal">e2</code>. This can be done using the
        <a class="link" href="programs.html#mysql" title="4.5.1 mysql — The MySQL Command-Line Tool"><span class="command"><strong>mysql</strong></span></a> client as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE e2 LIKE e;</code></strong>
Query OK, 0 rows affected (0.04 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE e2 REMOVE PARTITIONING;</code></strong>
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0
</pre><p>
        You can see which partitions in table <code class="literal">e</code>
        contain rows by querying the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
        table, like this:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, TABLE_ROWS</code></strong>
           <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
           <strong class="userinput"><code>WHERE TABLE_NAME = 'e';</code></strong>
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          1 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
2 rows in set (0.00 sec)
</pre>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          For partitioned <code class="literal">InnoDB</code> tables, the row
          count given in the <code class="literal">TABLE_ROWS</code> column of the
          <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
          table is only an estimated value used in SQL optimization, and
          is not always exact.
</p>
</div>
<p>
        To exchange partition <code class="literal">p0</code> in table
        <code class="literal">e</code> with table <code class="literal">e2</code>, you can
        use
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a>, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;</code></strong>
Query OK, 0 rows affected (0.04 sec)
</pre><p>
        More precisely, the statement just issued causes any rows found
        in the partition to be swapped with those found in the table.
        You can observe how this has happened by querying the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
        table, as before. The table row that was previously found in
        partition <code class="literal">p0</code> is no longer present:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, TABLE_ROWS</code></strong>
           <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
           <strong class="userinput"><code>WHERE TABLE_NAME = 'e';</code></strong>
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.00 sec)
</pre><p>
        If you query table <code class="literal">e2</code>, you can see that the
        <span class="quote">“<span class="quote">missing</span>”</span> row can now be found there:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM e2;</code></strong>
+----+-------+-------+
| id | fname | lname |
+----+-------+-------+
| 16 | Frank | White |
+----+-------+-------+
1 row in set (0.00 sec)
</pre><p>
        The table to be exchanged with the partition does not
        necessarily have to be empty. To demonstrate this, we first
        insert a new row into table <code class="literal">e</code>, making sure
        that this row is stored in partition <code class="literal">p0</code> by
        choosing an <code class="literal">id</code> column value that is less than
        50, and verifying this afterward by querying the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">PARTITIONS</code></a> table:
      </p><pre data-lang="sql" class="programlisting">                                                          
mysql&gt; <strong class="userinput"><code>INSERT INTO e VALUES (41, "Michael", "Green");</code></strong>            
Query OK, 1 row affected (0.05 sec)                              

mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, TABLE_ROWS</code></strong>
           <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
           <strong class="userinput"><code>WHERE TABLE_NAME = 'e';            </code></strong>
+----------------+------------+             
| PARTITION_NAME | TABLE_ROWS |             
+----------------+------------+             
| p0             |          1 |             
| p1             |          0 |             
| p2             |          0 |             
| p3             |          3 |             
+----------------+------------+             
4 rows in set (0.00 sec)        
</pre><p>
        Now we once again exchange partition <code class="literal">p0</code> with
        table <code class="literal">e2</code> using the same
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement as previously:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;</code></strong>
Query OK, 0 rows affected (0.28 sec)
</pre><p>
        The output of the following queries shows that the table row
        that was stored in partition <code class="literal">p0</code> and the table
        row that was stored in table <code class="literal">e2</code>, prior to
        issuing the
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement, have now switched places:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM e;</code></strong>
+------+-------+-------+
| id   | fname | lname |
+------+-------+-------+
|   16 | Frank | White |
| 1669 | Jim   | Smith |
|  337 | Mary  | Jones |
| 2005 | Linda | Black |
+------+-------+-------+
4 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, TABLE_ROWS</code></strong>
           <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
           <strong class="userinput"><code>WHERE TABLE_NAME = 'e';</code></strong>
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          1 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM e2;</code></strong>
+----+---------+-------+
| id | fname   | lname |
+----+---------+-------+
| 41 | Michael | Green |
+----+---------+-------+
1 row in set (0.00 sec)
</pre>
<h4><a name="idm139899483224784"></a>Nonmatching Rows</h4>
<p>
        You should keep in mind that any rows found in the
        nonpartitioned table prior to issuing the
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a> statement must meet the conditions
        required for them to be stored in the target partition;
        otherwise, the statement fails. To see how this occurs, first
        insert a row into <code class="literal">e2</code> that is outside the
        boundaries of the partition definition for partition
        <code class="literal">p0</code> of table <code class="literal">e</code>. For
        example, insert a row with an <code class="literal">id</code> column value
        that is too large; then, try to exchange the table with the
        partition again:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO e2 VALUES (51, "Ellen", "McDonald");</code></strong>
Query OK, 1 row affected (0.08 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;</code></strong>
<span class="errortext">ERROR 1707 (HY000): Found row that does not match the partition</span>
</pre><p>
        Only the <code class="literal">WITHOUT VALIDATION</code> option would
        permit this operation to succeed:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2 WITHOUT VALIDATION;</code></strong>
Query OK, 0 rows affected (0.02 sec)
</pre><p>
        When a partition is exchanged with a table that contains rows
        that do not match the partition definition, it is the
        responsibility of the database administrator to fix the
        non-matching rows, which can be performed using
        <a class="link" href="sql-syntax.html#repair-table" title="13.7.3.5 REPAIR TABLE Syntax"><code class="literal">REPAIR TABLE</code></a> or
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... REPAIR PARTITION</code></a>.
</p>
<h4><a name="idm139899483210112"></a>Exchanging Partitions Without Row-By-Row Validation</h4>
<p>
        To avoid time consuming validation when exchanging a partition
        with a table that has many rows, it is possible to skip the
        row-by-row validation step by appending <code class="literal">WITHOUT
        VALIDATION</code> to the
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... EXCHANGE PARTITION</code></a> statement.
      </p><p>
        The following example compares the difference between execution
        times when exchanging a partition with a nonpartitioned table,
        with and without validation. The partitioned table (table
        <code class="literal">e</code>) contains two partitions of 1 million rows
        each. The rows in p0 of table e are removed and p0 is exchanged
        with a nonpartitioned table of 1 million rows. The <code class="literal">WITH
        VALIDATION</code> operation takes 0.74 seconds. By
        comparison, the <code class="literal">WITHOUT VALIDATION</code> operation
        takes 0.01 seconds.
      </p><pre data-lang="sql" class="programlisting">
# Create a partitioned table with 1 million rows in each partition

CREATE TABLE e (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30)
)
    PARTITION BY RANGE (id) (
        PARTITION p0 VALUES LESS THAN (1000001),
        PARTITION p1 VALUES LESS THAN (2000001),
);

mysql&gt; SELECT COUNT(*) FROM e;                                             
| COUNT(*) |
+----------+
|  2000000 |
+----------+
1 row in set (0.27 sec)

# View the rows in each partition

SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+-------------+
| PARTITION_NAME | TABLE_ROWS  |
+----------------+-------------+
| p0             |     1000000 |
| p1             |     1000000 |
+----------------+-------------+
2 rows in set (0.00 sec)

# Create a nonpartitioned table of the same structure and populate it with 1 million rows

CREATE TABLE e2 (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30)
);

mysql&gt; SELECT COUNT(*) FROM e2;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.24 sec)

# Create another nonpartitioned table of the same structure and populate it with 1 million rows

CREATE TABLE e3 (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30)
);
    
mysql&gt; SELECT COUNT(*) FROM e3;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.25 sec)

# Drop the rows from p0 of table e

mysql&gt; DELETE FROM e WHERE id &lt; 1000001;
Query OK, 1000000 rows affected (5.55 sec)

# Confirm that there are no rows in partition p0

mysql&gt; SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)
    
# Exchange partition p0 of table e with the table e2 'WITH VALIDATION'

mysql&gt; ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2 WITH VALIDATION;
Query OK, 0 rows affected (0.74 sec)

# Confirm that the partition was exchanged with table e2

mysql&gt; SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |    1000000 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)

# Once again, drop the rows from p0 of table e

mysql&gt; DELETE FROM e WHERE id &lt; 1000001;
Query OK, 1000000 rows affected (5.55 sec)

# Confirm that there are no rows in partition p0

mysql&gt; SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)

# Exchange partition p0 of table e with the table e3 'WITHOUT VALIDATION'

mysql&gt; ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e3 WITHOUT VALIDATION;
Query OK, 0 rows affected (0.01 sec)

# Confirm that the partition was exchanged with table e3

mysql&gt; SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |    1000000 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)    
      </pre><p>
        If a partition is exchanged with a table that contains rows that
        do not match the partition definition, it is the responsibility
        of the database administrator to fix the non-matching rows,
        which can be performed using <a class="link" href="sql-syntax.html#repair-table" title="13.7.3.5 REPAIR TABLE Syntax"><code class="literal">REPAIR
        TABLE</code></a> or
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... REPAIR PARTITION</code></a>.
</p>
<h4><a name="idm139899483194352"></a>Exchanging a Subpartition with a Nonpartitioned Table</h4>
<p>
        You can also exchange a subpartition of a subpartitioned table
        (see <a class="xref" href="partitioning.html#partitioning-subpartitions" title="22.2.6 Subpartitioning">Section 22.2.6, “Subpartitioning”</a>) with a
        nonpartitioned table using an
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        EXCHANGE PARTITION</code></a> statement. In the following
        example, we first create a table <code class="literal">es</code> that is
        partitioned by <code class="literal">RANGE</code> and subpartitioned by
        <code class="literal">KEY</code>, populate this table as we did table
        <code class="literal">e</code>, and then create an empty, nonpartitioned
        copy <code class="literal">es2</code> of the table, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE es (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>fname VARCHAR(30),</code></strong>
    -&gt;     <strong class="userinput"><code>lname VARCHAR(30)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION BY RANGE (id)</code></strong>
    -&gt;     <strong class="userinput"><code>SUBPARTITION BY KEY (lname)</code></strong>
    -&gt;     <strong class="userinput"><code>SUBPARTITIONS 2 (</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (50),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (100),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (150),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (MAXVALUE)</code></strong>
    -&gt;     <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (2.76 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO es VALUES</code></strong>
    -&gt;     <strong class="userinput"><code>(1669, "Jim", "Smith"),</code></strong>
    -&gt;     <strong class="userinput"><code>(337, "Mary", "Jones"),</code></strong>
    -&gt;     <strong class="userinput"><code>(16, "Frank", "White"),</code></strong>
    -&gt;     <strong class="userinput"><code>(2005, "Linda", "Black");</code></strong>
Query OK, 4 rows affected (0.04 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>CREATE TABLE es2 LIKE es;</code></strong>
Query OK, 0 rows affected (1.27 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE es2 REMOVE PARTITIONING;</code></strong>
Query OK, 0 rows affected (0.70 sec)
Records: 0  Duplicates: 0  Warnings: 0
</pre><p>
        Although we did not explicitly name any of the subpartitions
        when creating table <code class="literal">es</code>, we can obtain
        generated names for these by including the
        <code class="literal">SUBPARTITION_NAME</code> column of the
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">PARTITIONS</code></a> table from
        <code class="literal">INFORMATION_SCHEMA</code> when selecting from that
        table, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, SUBPARTITION_NAME, TABLE_ROWS</code></strong>
    -&gt;     <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE TABLE_NAME = 'es';</code></strong>
+----------------+-------------------+------------+
| PARTITION_NAME | SUBPARTITION_NAME | TABLE_ROWS |
+----------------+-------------------+------------+
| p0             | p0sp0             |          1 |
| p0             | p0sp1             |          0 |
| p1             | p1sp0             |          0 |
| p1             | p1sp1             |          0 |
| p2             | p2sp0             |          0 |
| p2             | p2sp1             |          0 |
| p3             | p3sp0             |          3 |
| p3             | p3sp1             |          0 |
+----------------+-------------------+------------+
8 rows in set (0.00 sec)
</pre><p>
        The following
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement exchanges subpartition
        <code class="literal">p3sp0</code> in table <code class="literal">es</code> with the
        nonpartitioned table <code class="literal">es2</code>:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es2;</code></strong>
Query OK, 0 rows affected (0.29 sec)
</pre><p>
        You can verify that the rows were exchanged by issuing the
        following queries:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT PARTITION_NAME, SUBPARTITION_NAME, TABLE_ROWS</code></strong>
    -&gt;     <strong class="userinput"><code>FROM INFORMATION_SCHEMA.PARTITIONS</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE TABLE_NAME = 'es';</code></strong>
+----------------+-------------------+------------+
| PARTITION_NAME | SUBPARTITION_NAME | TABLE_ROWS |
+----------------+-------------------+------------+
| p0             | p0sp0             |          1 |
| p0             | p0sp1             |          0 |
| p1             | p1sp0             |          0 |
| p1             | p1sp1             |          0 |
| p2             | p2sp0             |          0 |
| p2             | p2sp1             |          0 |
| p3             | p3sp0             |          0 |
| p3             | p3sp1             |          0 |
+----------------+-------------------+------------+
8 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM es2;</code></strong>
+------+-------+-------+
| id   | fname | lname |
+------+-------+-------+
| 1669 | Jim   | Smith |
|  337 | Mary  | Jones |
| 2005 | Linda | Black |
+------+-------+-------+
3 rows in set (0.00 sec)
</pre><p>
        If a table is subpartitioned, you can exchange only a
        subpartition of the table—not an entire
        partition—with an unpartitioned table, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE es EXCHANGE PARTITION p3 WITH TABLE es2;</code></strong>
<span class="errortext">ERROR 1704 (HY000): Subpartitioned table, use subpartition instead of partition</span>
</pre><p>
        Table structures are compared in a strict fashion; the number,
        order, names, and types of columns and indexes of the
        partitioned table and the nonpartitioned table must match
        exactly. In addition, both tables must use the same storage
        engine:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE es3 LIKE e;</code></strong>
Query OK, 0 rows affected (1.31 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE es3 REMOVE PARTITIONING;</code></strong>
Query OK, 0 rows affected (0.53 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE es3\G</code></strong>
*************************** 1. row ***************************
       Table: es3
Create Table: CREATE TABLE `es3` (
  `id` int(11) NOT NULL,
  `fname` varchar(30) DEFAULT NULL,
  `lname` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>ALTER TABLE es3 ENGINE = MyISAM;</code></strong>
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es3;</code></strong>
<span class="errortext">ERROR 1497 (HY000): The mix of handlers in the partitions is not allowed in this version of MySQL</span>
</pre>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-maintenance"></a>22.3.4 Maintenance of Partitions</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483140704"></a><a class="indexterm" name="idm139899483139216"></a><a class="indexterm" name="idm139899483137728"></a><a class="indexterm" name="idm139899483136240"></a><a class="indexterm" name="idm139899483134752"></a><a class="indexterm" name="idm139899483133264"></a><a class="indexterm" name="idm139899483131776"></a><a class="indexterm" name="idm139899483130288"></a><p>
        A number of table and partition maintenance tasks can be carried
        out on partitioned tables using SQL statements intended for such
        purposes.
      </p><p>
        Table maintenance of partitioned tables can be accomplished
        using the statements <a class="link" href="sql-syntax.html#check-table" title="13.7.3.2 CHECK TABLE Syntax"><code class="literal">CHECK TABLE</code></a>,
        <a class="link" href="sql-syntax.html#optimize-table" title="13.7.3.4 OPTIMIZE TABLE Syntax"><code class="literal">OPTIMIZE TABLE</code></a>,
        <a class="link" href="sql-syntax.html#analyze-table" title="13.7.3.1 ANALYZE TABLE Syntax"><code class="literal">ANALYZE TABLE</code></a>, and
        <a class="link" href="sql-syntax.html#repair-table" title="13.7.3.5 REPAIR TABLE Syntax"><code class="literal">REPAIR TABLE</code></a>, which are supported
        for partitioned tables.
      </p><p>
        You can use a number of extensions to
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> for performing operations of this type on one or
        more partitions directly, as described in the following list:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p><b>Rebuilding partitions. </b>
              Rebuilds the partition; this has the same effect as
              dropping all records stored in the partition, then
              reinserting them. This can be useful for purposes of
              defragmentation.
            </p><p>
            Example:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE t1 REBUILD PARTITION p0, p1;
</pre></li><li class="listitem"><p><b>Optimizing partitions. </b>
              If you have deleted a large number of rows from a
              partition or if you have made many changes to a
              partitioned table with variable-length rows (that is,
              having <a class="link" href="data-types.html#char" title="11.4.1 The CHAR and VARCHAR Types"><code class="literal">VARCHAR</code></a>,
              <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">BLOB</code></a>, or
              <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">TEXT</code></a> columns), you can use
              <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
              TABLE ... OPTIMIZE PARTITION</code></a> to reclaim any
              unused space and to defragment the partition data file.
            </p><p>
            Example:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;
</pre><p>
            Using <code class="literal">OPTIMIZE PARTITION</code> on a given
            partition is equivalent to running <code class="literal">CHECK
            PARTITION</code>, <code class="literal">ANALYZE PARTITION</code>,
            and <code class="literal">REPAIR PARTITION</code> on that partition.
          </p><p>
            Some MySQL storage engines, including
            <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a>, do not support
            per-partition optimization; in these cases,
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            TABLE ... OPTIMIZE PARTITION</code></a> analyzes and rebuilds
            the entire table, and causes an appropriate warning to be
            issued. (Bug #11751825, Bug #42822) Use <code class="literal">ALTER TABLE
            ... REBUILD PARTITION</code> and <code class="literal">ALTER TABLE ...
            ANALYZE PARTITION</code> instead, to avoid this issue.
          </p></li><li class="listitem"><p><b>Analyzing partitions. </b>
              This reads and stores the key distributions for
              partitions.
            </p><p>
            Example:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE t1 ANALYZE PARTITION p3;
</pre></li><li class="listitem"><p><b>Repairing partitions. </b>
              This repairs corrupted partitions.
            </p><p>
            Example:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE t1 REPAIR PARTITION p0,p1;
</pre><p>
            Normally, <code class="literal">REPAIR PARTITION</code> fails when the
            partition contains duplicate key errors. You can use
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            IGNORE TABLE</code></a> with this option, in which case all
            rows that cannot be moved due to the presence of duplicate
            keys are removed from the partition (Bug #16900947).
          </p></li><li class="listitem"><p><b>Checking partitions. </b>
              You can check partitions for errors in much the same way
              that you can use <code class="literal">CHECK TABLE</code> with
              nonpartitioned tables.
            </p><p>
            Example:
          </p><pre data-lang="sql" class="programlisting">
ALTER TABLE trb3 CHECK PARTITION p1;
</pre><p>
            This command will tell you whether the data or indexes in
            partition <code class="literal">p1</code> of table
            <code class="literal">t1</code> are corrupted. If this is the case,
            use
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            TABLE ... REPAIR PARTITION</code></a> to repair the
            partition.
          </p><p>
            Normally, <code class="literal">CHECK PARTITION</code> fails when the
            partition contains duplicate key errors. You can use
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            IGNORE TABLE</code></a> with this option, in which case the
            statement returns the contents of each row in the partition
            where a duplicate key violation is found. Only the values
            for the columns in the partitioning expression for the table
            are reported. (Bug #16900947)
</p></li></ul>
</div>
<p>
        Each of the statements in the list just shown also supports the
        keyword <code class="literal">ALL</code> in place of the list of partition
        names. Using <code class="literal">ALL</code> causes the statement to act
        on all partitions in the table.
      </p><p>
        You can also truncate partitions using
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        TRUNCATE PARTITION</code></a>. This statement can be used to
        delete all rows from one or more partitions in much the same way
        that <a class="link" href="sql-syntax.html#truncate-table" title="13.1.34 TRUNCATE TABLE Syntax"><code class="literal">TRUNCATE TABLE</code></a> deletes all
        rows from a table.
      </p><p>
        <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE ...
        TRUNCATE PARTITION ALL</code></a> truncates all partitions in the
        table.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-info"></a>22.3.5 Obtaining Information About Partitions</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483074336"></a><a class="indexterm" name="idm139899483073280"></a><a class="indexterm" name="idm139899483072192"></a><a class="indexterm" name="idm139899483071104"></a><p>
        This section discusses obtaining information about existing
        partitions, which can be done in a number of ways. Methods of
        obtaining such information include the following:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            Using the <a class="link" href="sql-syntax.html#show-create-table" title="13.7.6.10 SHOW CREATE TABLE Syntax"><code class="literal">SHOW CREATE TABLE</code></a>
            statement to view the partitioning clauses used in creating
            a partitioned table.
          </p></li><li class="listitem"><p>
            Using the <a class="link" href="sql-syntax.html#show-table-status" title="13.7.6.36 SHOW TABLE STATUS Syntax"><code class="literal">SHOW TABLE STATUS</code></a>
            statement to determine whether a table is partitioned.
          </p></li><li class="listitem"><p>
            Querying the
            <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">INFORMATION_SCHEMA.PARTITIONS</code></a>
            table.
          </p></li><li class="listitem"><p>
            Using the statement
            <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN
            SELECT</code></a> to see which partitions are used by a given
            <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>.
</p></li></ul>
</div>
<p>
        As discussed elsewhere in this chapter,
        <a class="link" href="sql-syntax.html#show-create-table" title="13.7.6.10 SHOW CREATE TABLE Syntax"><code class="literal">SHOW CREATE TABLE</code></a> includes in its
        output the <code class="literal">PARTITION BY</code> clause used to create
        a partitioned table. For example:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE trb3\G</code></strong>
*************************** 1. row ***************************
       Table: trb3
Create Table: CREATE TABLE `trb3` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY RANGE (YEAR(purchased))
(PARTITION p0 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1995) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2005) ENGINE = InnoDB) */
0 row in set (0.00 sec)
</pre><p>
        The output from <a class="link" href="sql-syntax.html#show-table-status" title="13.7.6.36 SHOW TABLE STATUS Syntax"><code class="literal">SHOW TABLE STATUS</code></a>
        for partitioned tables is the same as that for nonpartitioned
        tables, except that the <code class="literal">Create_options</code> column
        contains the string <code class="literal">partitioned</code>. The
        <code class="literal">Engine</code> column contains the name of the
        storage engine used by all partitions of the table. (See
        <a class="xref" href="sql-syntax.html#show-table-status" title="13.7.6.36 SHOW TABLE STATUS Syntax">Section 13.7.6.36, “SHOW TABLE STATUS Syntax”</a>, for more information about
        this statement.)
      </p><p>
        You can also obtain information about partitions from
        <code class="literal">INFORMATION_SCHEMA</code>, which contains a
        <a class="link" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table"><code class="literal">PARTITIONS</code></a> table. See
        <a class="xref" href="information-schema.html#partitions-table" title="24.14 The INFORMATION_SCHEMA PARTITIONS Table">Section 24.14, “The INFORMATION_SCHEMA PARTITIONS Table”</a>.
      </p><a class="indexterm" name="idm139899483046304"></a><a class="indexterm" name="idm139899483044816"></a><p>
        It is possible to determine which partitions of a partitioned
        table are involved in a given
        <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> query using
        <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN</code></a>. The
        <code class="literal">partitions</code> column in the
        <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN</code></a> output lists the
        partitions from which records would be matched by the query.
      </p><p>
        Suppose that a table <code class="literal">trb1</code> is created and
        populated as follows:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE trb1 (id INT, name VARCHAR(50), purchased DATE)
    PARTITION BY RANGE(id)
    (
        PARTITION p0 VALUES LESS THAN (3),
        PARTITION p1 VALUES LESS THAN (7),
        PARTITION p2 VALUES LESS THAN (9),
        PARTITION p3 VALUES LESS THAN (11)
    );

INSERT INTO trb1 VALUES
    (1, 'desk organiser', '2003-10-15'),
    (2, 'CD player', '1993-11-05'),
    (3, 'TV set', '1996-03-10'),
    (4, 'bookcase', '1982-01-10'),
    (5, 'exercise bike', '2004-05-09'),
    (6, 'sofa', '1987-06-05'),
    (7, 'popcorn maker', '2001-11-22'),
    (8, 'aquarium', '1992-08-04'),
    (9, 'study desk', '1984-09-16'),
    (10, 'lava lamp', '1998-12-25');
</pre><p>
        You can see which partitions are used in a query such as
        <code class="literal">SELECT * FROM trb1;</code>, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>EXPLAIN SELECT * FROM trb1\G</code></strong>
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1,p2,p3
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
        Extra: Using filesort
</pre><p>
        In this case, all four partitions are searched. However, when a
        limiting condition making use of the partitioning key is added
        to the query, you can see that only those partitions containing
        matching values are scanned, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>EXPLAIN SELECT * FROM trb1 WHERE id &lt; 5\G</code></strong>
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
        Extra: Using where
</pre><p>
        <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN </code></a> also provides
        information about keys used and possible keys:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE trb1 ADD PRIMARY KEY (id);</code></strong>
Query OK, 10 rows affected (0.03 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>EXPLAIN SELECT * FROM trb1 WHERE id &lt; 5\G</code></strong>
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 7
        Extra: Using where
</pre><p>
        If <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN</code></a> is used to examine a
        query against a nonpartitioned table, no error is produced, but
        the value of the <code class="literal">partitions</code> column is always
        <code class="literal">NULL</code>.
      </p><p>
        The <code class="literal">rows</code> column of
        <a class="link" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax"><code class="literal">EXPLAIN</code></a> output displays the total
        number of rows in the table.
      </p><p>
        See also <a class="xref" href="sql-syntax.html#explain" title="13.8.2 EXPLAIN Syntax">Section 13.8.2, “EXPLAIN Syntax”</a>.
</p>
</div>

</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-pruning"></a>22.4 Partition Pruning</h2>

</div>

</div>

</div>
<a class="indexterm" name="idm139899483017600"></a><a class="indexterm" name="idm139899483016528"></a><p>
      The optimization known as <span class="firstterm">partition
      pruning</span> is based on a relatively simple concept which
      can be described as <span class="quote">“<span class="quote">Do not scan partitions where there can
      be no matching values</span>”</span>. Suppose a partitioned table
      <code class="literal">t1</code> is created by this statement:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t1 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY RANGE( region_code ) (
    PARTITION p0 VALUES LESS THAN (64),
    PARTITION p1 VALUES LESS THAN (128),
    PARTITION p2 VALUES LESS THAN (192),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);
</pre><p>
      Suppose that you wish to obtain results from a
      <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> statement such as this one:
    </p><pre data-lang="sql" class="programlisting">
SELECT fname, lname, region_code, dob
    FROM t1
    WHERE region_code &gt; 125 AND region_code &lt; 130;
</pre><p>
      It is easy to see that none of the rows which ought to be returned
      are in either of the partitions <code class="literal">p0</code> or
      <code class="literal">p3</code>; that is, we need search only in partitions
      <code class="literal">p1</code> and <code class="literal">p2</code> to find matching
      rows. By limiting the search, it is possible to expend much less
      time and effort in finding matching rows than by scanning all
      partitions in the table. This <span class="quote">“<span class="quote">cutting away</span>”</span> of
      unneeded partitions is known as
      <span class="firstterm">pruning</span>. When the optimizer
      can make use of partition pruning in performing this query,
      execution of the query can be an order of magnitude faster than
      the same query against a nonpartitioned table containing the same
      column definitions and data.
    </p><p>
      The optimizer can perform pruning whenever a
      <code class="literal">WHERE</code> condition can be reduced to either one of
      the following two cases:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          <code class="literal"><em class="replaceable"><code>partition_column</code></em> =
          <em class="replaceable"><code>constant</code></em></code>
        </p></li><li class="listitem"><p>
          <code class="literal"><em class="replaceable"><code>partition_column</code></em> IN
          (<em class="replaceable"><code>constant1</code></em>,
          <em class="replaceable"><code>constant2</code></em>, ...,
          <em class="replaceable"><code>constantN</code></em>)</code>
</p></li></ul>
</div>
<p>
      In the first case, the optimizer simply evaluates the partitioning
      expression for the value given, determines which partition
      contains that value, and scans only this partition. In many cases,
      the equal sign can be replaced with another arithmetic comparison,
      including <code class="literal">&lt;</code>, <code class="literal">&gt;</code>,
      <code class="literal">&lt;=</code>, <code class="literal">&gt;=</code>, and
      <code class="literal">&lt;&gt;</code>. Some queries using
      <code class="literal">BETWEEN</code> in the <code class="literal">WHERE</code> clause
      can also take advantage of partition pruning. See the examples
      later in this section.
    </p><p>
      In the second case, the optimizer evaluates the partitioning
      expression for each value in the list, creates a list of matching
      partitions, and then scans only the partitions in this partition
      list.
    </p><p>
      <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>,
      <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a>, and
      <a class="link" href="sql-syntax.html#update" title="13.2.12 UPDATE Syntax"><code class="literal">UPDATE</code></a> statements support partition
      pruning. <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statements
      currently cannot be pruned.
    </p><p>
      Pruning can also be applied to short ranges, which the optimizer
      can convert into equivalent lists of values. For instance, in the
      previous example, the <code class="literal">WHERE</code> clause can be
      converted to <code class="literal">WHERE region_code IN (126, 127, 128,
      129)</code>. Then the optimizer can determine that the first
      two values in the list are found in partition
      <code class="literal">p1</code>, the remaining two values in partition
      <code class="literal">p2</code>, and that the other partitions contain no
      relevant values and so do not need to be searched for matching
      rows.
    </p><p>
      The optimizer can also perform pruning for
      <code class="literal">WHERE</code> conditions that involve comparisons of
      the preceding types on multiple columns for tables that use
      <code class="literal">RANGE COLUMNS</code> or <code class="literal">LIST
      COLUMNS</code> partitioning.
    </p><a class="indexterm" name="idm139899482978736"></a><p>
      This type of optimization can be applied whenever the partitioning
      expression consists of an equality or a range which can be reduced
      to a set of equalities, or when the partitioning expression
      represents an increasing or decreasing relationship. Pruning can
      also be applied for tables partitioned on a
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> or
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> column when the
      partitioning expression uses the
      <a class="link" href="functions.html#function_year"><code class="literal">YEAR()</code></a> or
      <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS()</code></a> function. Pruning can
      also be applied for such tables when the partitioning expression
      uses the <a class="link" href="functions.html#function_to-seconds"><code class="literal">TO_SECONDS()</code></a> function.
    </p><p>
      Suppose that table <code class="literal">t2</code>, partitioned on a
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> column, is created using the
      statement shown here:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t2 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY RANGE( YEAR(dob) ) (
    PARTITION d0 VALUES LESS THAN (1970),
    PARTITION d1 VALUES LESS THAN (1975),
    PARTITION d2 VALUES LESS THAN (1980),
    PARTITION d3 VALUES LESS THAN (1985),
    PARTITION d4 VALUES LESS THAN (1990),
    PARTITION d5 VALUES LESS THAN (2000),
    PARTITION d6 VALUES LESS THAN (2005),
    PARTITION d7 VALUES LESS THAN MAXVALUE
);
</pre><p>
      The following statements using <code class="literal">t2</code> can make of
      use partition pruning:
    </p><pre data-lang="sql" class="programlisting">
SELECT * FROM t2 WHERE dob = '1982-06-23';

UPDATE t2 SET region_code = 8 WHERE dob BETWEEN '1991-02-15' AND '1997-04-25';

DELETE FROM t2 WHERE dob &gt;= '1984-06-21' AND dob &lt;= '1999-06-21'
</pre><p>
      In the case of the last statement, the optimizer can also act as
      follows:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
          <span class="emphasis"><em>Find the partition containing the low end of the
          range</em></span>.
        </p><p>
          <a class="link" href="functions.html#function_year"><code class="literal">YEAR('1984-06-21')</code></a> yields the
          value <code class="literal">1984</code>, which is found in partition
          <code class="literal">d3</code>.
        </p></li><li class="listitem"><p>
          <span class="emphasis"><em>Find the partition containing the high end of the
          range</em></span>.
        </p><p>
          <a class="link" href="functions.html#function_year"><code class="literal">YEAR('1999-06-21')</code></a> evaluates to
          <code class="literal">1999</code>, which is found in partition
          <code class="literal">d5</code>.
        </p></li><li class="listitem"><p>
          <span class="emphasis"><em>Scan only these two partitions and any partitions
          that may lie between them</em></span>.
        </p><p>
          In this case, this means that only partitions
          <code class="literal">d3</code>, <code class="literal">d4</code>, and
          <code class="literal">d5</code> are scanned. The remaining partitions
          may be safely ignored (and are ignored).
</p></li></ol>
</div>
<div class="important" style="margin-left: 0.5in; margin-right: 0.5in;">
<div class="admon-title">
Important
</div>
<p>
        Invalid <code class="literal">DATE</code> and <code class="literal">DATETIME</code>
        values referenced in the <code class="literal">WHERE</code> condition of a
        statement against a partitioned table are treated as
        <code class="literal">NULL</code>. This means that a query such as
        <code class="literal">SELECT * FROM
        <em class="replaceable"><code>partitioned_table</code></em> WHERE
        <em class="replaceable"><code>date_column</code></em> &lt;
        '2008-12-00'</code> does not return any values (see Bug
        #40972).
</p>
</div>
<p>
      So far, we have looked only at examples using
      <code class="literal">RANGE</code> partitioning, but pruning can be applied
      with other partitioning types as well.
    </p><p>
      Consider a table that is partitioned by <code class="literal">LIST</code>,
      where the partitioning expression is increasing or decreasing,
      such as the table <code class="literal">t3</code> shown here. (In this
      example, we assume for the sake of brevity that the
      <code class="literal">region_code</code> column is limited to values between
      1 and 10 inclusive.)
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t3 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY LIST(region_code) (
    PARTITION r0 VALUES IN (1, 3),
    PARTITION r1 VALUES IN (2, 5, 8),
    PARTITION r2 VALUES IN (4, 9),
    PARTITION r3 VALUES IN (6, 7, 10)
);
</pre><p>
      For a statement such as <code class="literal">SELECT * FROM t3 WHERE
      region_code BETWEEN 1 AND 3</code>, the optimizer determines in
      which partitions the values 1, 2, and 3 are found
      (<code class="literal">r0</code> and <code class="literal">r1</code>) and skips the
      remaining ones (<code class="literal">r2</code> and <code class="literal">r3</code>).
    </p><p>
      For tables that are partitioned by <code class="literal">HASH</code> or
      <code class="literal">[LINEAR] KEY</code>, partition pruning is also
      possible in cases in which the <code class="literal">WHERE</code> clause
      uses a simple <code class="literal">=</code> relation against a column used
      in the partitioning expression. Consider a table created like
      this:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t4 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY KEY(region_code)
PARTITIONS 8;
</pre><p>
      A statement that compares a column value with a constant can be
      pruned:
    </p><pre data-lang="sql" class="programlisting">
UPDATE t4 WHERE region_code = 7;
</pre><p>
      Pruning can also be employed for short ranges, because the
      optimizer can turn such conditions into <code class="literal">IN</code>
      relations. For example, using the same table <code class="literal">t4</code>
      as defined previously, queries such as these can be pruned:
    </p><pre data-lang="sql" class="programlisting">
SELECT * FROM t4 WHERE region_code &gt; 2 AND region_code &lt; 6;

SELECT * FROM t4 WHERE region_code BETWEEN 3 AND 5;
</pre><p>
      In both these cases, the <code class="literal">WHERE</code> clause is
      transformed by the optimizer into <code class="literal">WHERE region_code IN
      (3, 4, 5)</code>.
</p>
<div class="important" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Important
</div>
<p>
        This optimization is used only if the range size is smaller than
        the number of partitions. Consider this statement:
      </p><pre data-lang="sql" class="programlisting">
DELETE FROM t4 WHERE region_code BETWEEN 4 AND 12;
</pre><p>
        The range in the <code class="literal">WHERE</code> clause covers 9 values
        (4, 5, 6, 7, 8, 9, 10, 11, 12), but <code class="literal">t4</code> has
        only 8 partitions. This means that the <code class="literal">DELETE</code>
        cannot be pruned.
</p>
</div>
<p>
      When a table is partitioned by <code class="literal">HASH</code> or
      <code class="literal">[LINEAR] KEY</code>, pruning can be used only on
      integer columns. For example, this statement cannot use pruning
      because <code class="literal">dob</code> is a
      <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a> column:
    </p><pre data-lang="sql" class="programlisting">
SELECT * FROM t4 WHERE dob &gt;= '2001-04-14' AND dob &lt;= '2005-10-15';
</pre><p>
      However, if the table stores year values in an
      <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">INT</code></a> column, then a query having
      <code class="literal">WHERE year_col &gt;= 2001 AND year_col &lt;=
      2005</code> can be pruned.
    </p><p>
      Tables using a storage engine that provides automatic
      partitioning, such as the <code class="literal">NDB</code> storage engine
      used by MySQL Cluster (not currently supported in MySQL
      8.0), can be pruned if they are explicitly
      partitioned.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-selection"></a>22.5 Partition Selection</h2>

</div>

</div>

</div>
<p>
      Explicit selection of partitions and subpartitions for rows
      matching a given <code class="literal">WHERE</code> condition is supported.
      Partition selection is similar to partition pruning, in that only
      specific partitions are checked for matches, but differs in two
      key respects:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
          The partitions to be checked are specified by the issuer of
          the statement, unlike partition pruning, which is automatic.
        </p></li><li class="listitem"><p>
          Whereas partition pruning applies only to queries, explicit
          selection of partitions is supported for both queries and a
          number of DML statements.
</p></li></ol>
</div>
<p>
      SQL statements supporting explicit partition selection are listed
      here:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a>
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a>
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax"><code class="literal">REPLACE</code></a>
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#update" title="13.2.12 UPDATE Syntax"><code class="literal">UPDATE</code></a>
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#load-data" title="13.2.7 LOAD DATA INFILE Syntax"><code class="literal">LOAD DATA</code></a>.
        </p></li><li class="listitem"><p>
          <a class="link" href="sql-syntax.html#load-xml" title="13.2.8 LOAD XML Syntax"><code class="literal">LOAD XML</code></a>.
</p></li></ul>
</div>
<p>
      The remainder of this section discusses explicit partition
      selection as it applies generally to the statements just listed,
      and provides some examples.
    </p><p>
      Explicit partition selection is implemented using a
      <code class="literal">PARTITION</code> option. For all supported statements,
      this option uses the syntax shown here:
    </p><pre data-lang="sql" class="programlisting">
      PARTITION (<em class="replaceable"><code>partition_names</code></em>)

      <em class="replaceable"><code>partition_names</code></em>:
          <em class="replaceable"><code>partition_name</code></em>, ...
</pre><p>
      This option always follows the name of the table to which the
      partition or partitions belong.
      <em class="replaceable"><code>partition_names</code></em> is a comma-separated
      list of partitions or subpartitions to be used. Each name in this
      list must be the name of an existing partition or subpartition of
      the specified table; if any of the partitions or subpartitions are
      not found, the statement fails with an error (<span class="errortext">partition
      '<em class="replaceable"><code>partition_name</code></em>' doesn't
      exist</span>). Partitions and subpartitions named in
      <em class="replaceable"><code>partition_names</code></em> may be listed in any
      order, and may overlap.
    </p><p>
      When the <code class="literal">PARTITION</code> option is used, only the
      partitions and subpartitions listed are checked for matching rows.
      This option can be used in a <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a>
      statement to determine which rows belong to a given partition.
      Consider a partitioned table named <code class="literal">employees</code>,
      created and populated using the statements shown here:
    </p><pre data-lang="sql" class="programlisting">
SET @@SQL_MODE = '';

CREATE TABLE employees  (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    fname VARCHAR(25) NOT NULL,
    lname VARCHAR(25) NOT NULL,
    store_id INT NOT NULL,
    department_id INT NOT NULL
) 
    PARTITION BY RANGE(id)  (
        PARTITION p0 VALUES LESS THAN (5),
        PARTITION p1 VALUES LESS THAN (10),
        PARTITION p2 VALUES LESS THAN (15),
        PARTITION p3 VALUES LESS THAN MAXVALUE
);

INSERT INTO employees VALUES
    ('', 'Bob', 'Taylor', 3, 2), ('', 'Frank', 'Williams', 1, 2),
    ('', 'Ellen', 'Johnson', 3, 4), ('', 'Jim', 'Smith', 2, 4),
    ('', 'Mary', 'Jones', 1, 1), ('', 'Linda', 'Black', 2, 3),
    ('', 'Ed', 'Jones', 2, 1), ('', 'June', 'Wilson', 3, 1),
    ('', 'Andy', 'Smith', 1, 3), ('', 'Lou', 'Waters', 2, 4),
    ('', 'Jill', 'Stone', 1, 4), ('', 'Roger', 'White', 3, 2),
    ('', 'Howard', 'Andrews', 1, 2), ('', 'Fred', 'Goldberg', 3, 3),
    ('', 'Barbara', 'Brown', 2, 3), ('', 'Alice', 'Rogers', 2, 2),
    ('', 'Mark', 'Morgan', 3, 3), ('', 'Karen', 'Cole', 3, 2);
</pre><p>
      You can see which rows are stored in partition
      <code class="literal">p1</code> like this:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM employees PARTITION (p1);</code></strong>
+----+-------+--------+----------+---------------+
| id | fname | lname  | store_id | department_id |
+----+-------+--------+----------+---------------+
|  5 | Mary  | Jones  |        1 |             1 |
|  6 | Linda | Black  |        2 |             3 |
|  7 | Ed    | Jones  |        2 |             1 |
|  8 | June  | Wilson |        3 |             1 |
|  9 | Andy  | Smith  |        1 |             3 |
+----+-------+--------+----------+---------------+
5 rows in set (0.00 sec)
</pre><p>
      The result is the same as obtained by the query <code class="literal">SELECT *
      FROM employees WHERE id BETWEEN 5 AND 9</code>.
    </p><p>
      To obtain rows from multiple partitions, supply their names as a
      comma-delimited list. For example, <code class="literal">SELECT * FROM
      employees PARTITION (p1, p2)</code> returns all rows from
      partitions <code class="literal">p1</code> and <code class="literal">p2</code> while
      excluding rows from the remaining partitions.
    </p><p>
      Any valid query against a partitioned table can be rewritten with
      a <code class="literal">PARTITION</code> option to restrict the result to
      one or more desired partitions. You can use
      <code class="literal">WHERE</code> conditions, <code class="literal">ORDER BY</code>
      and <code class="literal">LIMIT</code> options, and so on. You can also use
      aggregate functions with <code class="literal">HAVING</code> and
      <code class="literal">GROUP BY</code> options. Each of the following queries
      produces a valid result when run on the
      <code class="literal">employees</code> table as previously defined:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM employees PARTITION (p0, p2)</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE lname LIKE 'S%';</code></strong>
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
|  4 | Jim   | Smith |        2 |             4 |
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
2 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT id, CONCAT(fname, ' ', lname) AS name</code></strong>
    -&gt;     <strong class="userinput"><code>FROM employees PARTITION (p0) ORDER BY lname;</code></strong>
+----+----------------+
| id | name           |
+----+----------------+
|  3 | Ellen Johnson  |
|  4 | Jim Smith      |
|  1 | Bob Taylor     |
|  2 | Frank Williams |
+----+----------------+
4 rows in set (0.06 sec)

mysql&gt; <strong class="userinput"><code>SELECT store_id, COUNT(department_id) AS c</code></strong>
    -&gt;     <strong class="userinput"><code>FROM employees PARTITION (p1,p2,p3)</code></strong>
    -&gt;     <strong class="userinput"><code>GROUP BY store_id HAVING c &gt; 4;</code></strong>
+---+----------+
| c | store_id |
+---+----------+
| 5 |        2 |
| 5 |        3 |
+---+----------+
2 rows in set (0.00 sec)
</pre><p>
      Statements using partition selection can be employed with tables
      using any of the supported partitioning types. When a table is
      created using <code class="literal">[LINEAR] HASH</code> or
      <code class="literal">[LINEAR] KEY</code> partitioning and the names of the
      partitions are not specified, MySQL automatically names the
      partitions <code class="literal">p0</code>, <code class="literal">p1</code>,
      <code class="literal">p2</code>, ...,
      <code class="literal">p<em class="replaceable"><code>N-1</code></em></code>, where
      <em class="replaceable"><code>N</code></em> is the number of partitions. For
      subpartitions not explicitly named, MySQL assigns automatically to
      the subpartitions in each partition
      <code class="literal">p<em class="replaceable"><code>X</code></em></code> the names
      <code class="literal">p<em class="replaceable"><code>X</code></em>sp0</code>,
      <code class="literal">p<em class="replaceable"><code>X</code></em>sp1</code>,
      <code class="literal">p<em class="replaceable"><code>X</code></em>sp2</code>, ...,
      <code class="literal">p<em class="replaceable"><code>X</code></em>sp<em class="replaceable"><code>M-1</code></em></code>,
      where <em class="replaceable"><code>M</code></em> is the number of subpartitions.
      When executing against this table a
      <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> (or other SQL statement for
      which explicit partition selection is allowed), you can use these
      generated names in a <code class="literal">PARTITION</code> option, as shown
      here:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE employees_sub  (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT NOT NULL AUTO_INCREMENT,</code></strong>
    -&gt;     <strong class="userinput"><code>fname VARCHAR(25) NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>lname VARCHAR(25) NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>store_id INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>department_id INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>PRIMARY KEY pk (id, lname)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong> 
    -&gt;     <strong class="userinput"><code>PARTITION BY RANGE(id)</code></strong>
    -&gt;     <strong class="userinput"><code>SUBPARTITION BY KEY (lname)</code></strong>
    -&gt;     <strong class="userinput"><code>SUBPARTITIONS 2 (</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (5),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (10),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (15),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN MAXVALUE</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (1.14 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO employees_sub</code></strong>   # reuse data in employees table
    -&gt;     <strong class="userinput"><code>SELECT * FROM employees;</code></strong>
Query OK, 18 rows affected (0.09 sec)
Records: 18  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT id, CONCAT(fname, ' ', lname) AS name</code></strong>
    -&gt;     <strong class="userinput"><code>FROM employees_sub PARTITION (p2sp1);</code></strong>
+----+---------------+
| id | name          |
+----+---------------+
| 10 | Lou Waters    |
| 14 | Fred Goldberg |
+----+---------------+
2 rows in set (0.00 sec)
</pre><p>
      You may also use a <code class="literal">PARTITION</code> option in the
      <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> portion of an
      <a class="link" href="sql-syntax.html#insert-select" title="13.2.6.1 INSERT ... SELECT Syntax"><code class="literal">INSERT ...
      SELECT</code></a> statement, as shown here:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE employees_copy LIKE employees;</code></strong>
Query OK, 0 rows affected (0.28 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO employees_copy</code></strong>
    -&gt;     <strong class="userinput"><code>SELECT * FROM employees PARTITION (p2);</code></strong>
Query OK, 5 rows affected (0.04 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT * FROM employees_copy;</code></strong>
+----+--------+----------+----------+---------------+
| id | fname  | lname    | store_id | department_id |
+----+--------+----------+----------+---------------+
| 10 | Lou    | Waters   |        2 |             4 |
| 11 | Jill   | Stone    |        1 |             4 |
| 12 | Roger  | White    |        3 |             2 |
| 13 | Howard | Andrews  |        1 |             2 |
| 14 | Fred   | Goldberg |        3 |             3 |
+----+--------+----------+----------+---------------+
5 rows in set (0.00 sec)
</pre><p>
      Partition selection can also be used with joins. Suppose we create
      and populate two tables using the statements shown here:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE stores (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    city VARCHAR(30) NOT NULL
)
    PARTITION BY HASH(id)
    PARTITIONS 2;
  
INSERT INTO stores VALUES
    ('', 'Nambucca'), ('', 'Uranga'),
    ('', 'Bellingen'), ('', 'Grafton');
  
CREATE TABLE departments  (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30) NOT NULL
)
    PARTITION BY KEY(id)
    PARTITIONS 2;
  
INSERT INTO departments VALUES
    ('', 'Sales'), ('', 'Customer Service'),
    ('', 'Delivery'), ('', 'Accounting');
</pre><p>
      You can explicitly select partitions (or subpartitions, or both)
      from any or all of the tables in a join. (The
      <code class="literal">PARTITION</code> option used to select partitions from
      a given table immediately follows the name of the table, before
      all other options, including any table alias.) For example, the
      following query gets the name, employee ID, department, and city
      of all employees who work in the Sales or Delivery department
      (partition <code class="literal">p1</code> of the
      <code class="literal">departments</code> table) at the stores in either of
      the cities of Nambucca and Bellingen (partition
      <code class="literal">p0</code> of the <code class="literal">stores</code> table):
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT</code></strong>
    -&gt;     <strong class="userinput"><code>e.id AS 'Employee ID', CONCAT(e.fname, ' ', e.lname) AS Name,</code></strong>
    -&gt;     <strong class="userinput"><code>s.city AS City, d.name AS department</code></strong>
    -&gt; <strong class="userinput"><code>FROM employees AS e</code></strong>
    -&gt;     <strong class="userinput"><code>JOIN stores PARTITION (p1) AS s ON e.store_id=s.id</code></strong>
    -&gt;     <strong class="userinput"><code>JOIN departments PARTITION (p0) AS d ON e.department_id=d.id</code></strong>
    -&gt; <strong class="userinput"><code>ORDER BY e.lname;</code></strong>
+-------------+---------------+-----------+------------+
| Employee ID | Name          | City      | department |
+-------------+---------------+-----------+------------+
|          14 | Fred Goldberg | Bellingen | Delivery   |
|           5 | Mary Jones    | Nambucca  | Sales      |
|          17 | Mark Morgan   | Bellingen | Delivery   |
|           9 | Andy Smith    | Nambucca  | Delivery   |
|           8 | June Wilson   | Bellingen | Sales      |
+-------------+---------------+-----------+------------+
5 rows in set (0.00 sec)
</pre><p>
      For general information about joins in MySQL, see
      <a class="xref" href="sql-syntax.html#join" title="13.2.10.2 JOIN Syntax">Section 13.2.10.2, “JOIN Syntax”</a>.
    </p><p>
      When the <code class="literal">PARTITION</code> option is used with
      <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a> statements, only those
      partitions (and subpartitions, if any) listed with the option are
      checked for rows to be deleted. Any other partitions are ignored,
      as shown here:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT * FROM employees WHERE fname LIKE 'j%';</code></strong>
+----+-------+--------+----------+---------------+
| id | fname | lname  | store_id | department_id |
+----+-------+--------+----------+---------------+
|  4 | Jim   | Smith  |        2 |             4 |
|  8 | June  | Wilson |        3 |             1 |
| 11 | Jill  | Stone  |        1 |             4 |
+----+-------+--------+----------+---------------+
3 rows in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>DELETE FROM employees PARTITION (p0, p1)</code></strong>
    -&gt;     <strong class="userinput"><code>WHERE fname LIKE 'j%';</code></strong>
Query OK, 2 rows affected (0.09 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM employees WHERE fname LIKE 'j%';</code></strong>
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)
</pre><p>
      Only the two rows in partitions <code class="literal">p0</code> and
      <code class="literal">p1</code> matching the <code class="literal">WHERE</code>
      condition were deleted. As you can see from the result when the
      <a class="link" href="sql-syntax.html#select" title="13.2.10 SELECT Syntax"><code class="literal">SELECT</code></a> is run a second time, there
      remains a row in the table matching the <code class="literal">WHERE</code>
      condition, but residing in a different partition
      (<code class="literal">p2</code>).
    </p><p>
      <a class="link" href="sql-syntax.html#update" title="13.2.12 UPDATE Syntax"><code class="literal">UPDATE</code></a> statements using explicit
      partition selection behave in the same way; only rows in the
      partitions referenced by the <code class="literal">PARTITION</code> option
      are considered when determining the rows to be updated, as can be
      seen by executing the following statements:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>UPDATE employees PARTITION (p0) </code></strong>
    -&gt;     <strong class="userinput"><code>SET store_id = 2 WHERE fname = 'Jill';</code></strong>
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT * FROM employees WHERE fname = 'Jill';</code></strong>
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>UPDATE employees PARTITION (p2)</code></strong>
    -&gt;     <strong class="userinput"><code>SET store_id = 2 WHERE fname = 'Jill';</code></strong>
Query OK, 1 row affected (0.09 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql&gt; <strong class="userinput"><code>SELECT * FROM employees WHERE fname = 'Jill';</code></strong>
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        2 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)
</pre><p>
      In the same way, when <code class="literal">PARTITION</code> is used with
      <a class="link" href="sql-syntax.html#delete" title="13.2.2 DELETE Syntax"><code class="literal">DELETE</code></a>, only rows in the partition
      or partitions named in the partition list are checked for
      deletion.
    </p><p>
      For statements that insert rows, the behavior differs in that
      failure to find a suitable partition causes the statement to fail.
      This is true for both <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> and
      <a class="link" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax"><code class="literal">REPLACE</code></a> statements, as shown here:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>INSERT INTO employees PARTITION (p2) VALUES (20, 'Jan', 'Jones', 1, 3);</code></strong>
<span class="errortext">ERROR 1729 (HY000): Found a row not matching the given partition set</span>
mysql&gt; <strong class="userinput"><code>INSERT INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 1, 3);</code></strong>
Query OK, 1 row affected (0.07 sec)

mysql&gt; REPLACE INTO employees PARTITION (p0) VALUES (20, 'Jan', 'Jones', 3, 2);
<span class="errortext">ERROR 1729 (HY000): Found a row not matching the given partition set</span>

mysql&gt; REPLACE INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 3, 2);
Query OK, 2 rows affected (0.09 sec)
</pre><p>
      For statements that write multiple rows to a partitioned table
      that using the <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a> storage engine:
      If any row in the list following <code class="literal">VALUES</code> cannot
      be written to one of the partitions specified in the
      <em class="replaceable"><code>partition_names</code></em> list, the entire
      statement fails and no rows are written. This is shown for
      <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statements in the following
      example, reusing the <code class="literal">employees</code> table created
      previously:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE employees</code></strong>
    -&gt;     <strong class="userinput"><code>REORGANIZE PARTITION p3 INTO (</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (20),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p4 VALUES LESS THAN (25),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p5 VALUES LESS THAN MAXVALUE</code></strong>
    -&gt;     <strong class="userinput"><code>);</code></strong>
Query OK, 6 rows affected (2.09 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql&gt; <strong class="userinput"><code>SHOW CREATE TABLE employees\G</code></strong>
*************************** 1. row ***************************
       Table: employees
Create Table: CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `fname` varchar(25) NOT NULL,
  `lname` varchar(25) NOT NULL,
  `store_id` int(11) NOT NULL,
  `department_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY RANGE (id)
(PARTITION p0 VALUES LESS THAN (5) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (10) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (15) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (20) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (25) ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>INSERT INTO employees PARTITION (p3, p4) VALUES</code></strong>
    -&gt;     <strong class="userinput"><code>(24, 'Tim', 'Greene', 3, 1),  (26, 'Linda', 'Mills', 2, 1);</code></strong>
ERROR 1729 (HY000): Found a row not matching the given partition set

mysql&gt; <strong class="userinput"><code>INSERT INTO employees PARTITION (p3, p4. p5) VALUES</code></strong>
    -&gt;     <strong class="userinput"><code>(24, 'Tim', 'Greene', 3, 1),  (26, 'Linda', 'Mills', 2, 1);</code></strong>
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0
</pre><p>
      The preceding is true for both
      <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> statements and
      <a class="link" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax"><code class="literal">REPLACE</code></a> statements that write
      multiple rows.
    </p><p>
      Partition selection is disabled for tables employing a storage
      engine that supplies automatic partitioning, such as
      <code class="literal">NDB</code> (used with MySQL Cluster; not currently
      supported in MySQL 8.0).
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a name="partitioning-limitations"></a>22.6 Restrictions and Limitations on Partitioning</h2>

</div>

</div>

</div>
<div class="toc">
<dl class="toc"><dt><span class="section"><a href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys">22.6.1 Partitioning Keys, Primary Keys, and Unique Keys</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-limitations-storage-engines">22.6.2 Partitioning Limitations Relating to Storage Engines</a></span></dt><dt><span class="section"><a href="partitioning.html#partitioning-limitations-functions">22.6.3 Partitioning Limitations Relating to Functions</a></span></dt></dl>
</div>
<a class="indexterm" name="idm139899482752272"></a><p>
      This section discusses current restrictions and limitations on
      MySQL partitioning support.
    </p><p><b>Prohibited constructs. </b>
        The following constructs are not permitted in partitioning
        expressions:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
          Stored procedures, stored functions, UDFs, or plugins.
        </p></li><li class="listitem"><p>
          Declared variables or user variables.
</p></li></ul>
</div>
<p>
      For a list of SQL functions which are permitted in partitioning
      expressions, see
      <a class="xref" href="partitioning.html#partitioning-limitations-functions" title="22.6.3 Partitioning Limitations Relating to Functions">Section 22.6.3, “Partitioning Limitations Relating to Functions”</a>.
    </p><p><b>Arithmetic and logical operators. </b>
        <a class="indexterm" name="idm139899482744784"></a>

        <a class="indexterm" name="idm139899482743264"></a>

        Use of the arithmetic operators
        <a class="link" href="functions.html#operator_plus"><code class="literal">+</code></a>,
        <a class="link" href="functions.html#operator_minus"><code class="literal">-</code></a>, and
        <a class="link" href="functions.html#operator_times"><code class="literal">*</code></a> is permitted in
        partitioning expressions. However, the result must be an integer
        value or <code class="literal">NULL</code> (except in the case of
        <code class="literal">[LINEAR] KEY</code> partitioning, as discussed
        elsewhere in this chapter; see
        <a class="xref" href="partitioning.html#partitioning-types" title="22.2 Partitioning Types">Section 22.2, “Partitioning Types”</a>, for more information).
      </p><p>
      The <a class="link" href="functions.html#operator_div"><code class="literal">DIV</code></a> operator is also supported;
      the <a class="link" href="functions.html#operator_divide"><code class="literal">/</code></a> operator is
      not permitted.
    </p><p>
      The bit operators
      <a class="link" href="functions.html#operator_bitwise-or"><code class="literal">|</code></a>,
      <a class="link" href="functions.html#operator_bitwise-and"><code class="literal">&amp;</code></a>,
      <a class="link" href="functions.html#operator_bitwise-xor"><code class="literal">^</code></a>,
      <a class="link" href="functions.html#operator_left-shift"><code class="literal">&lt;&lt;</code></a>,
      <a class="link" href="functions.html#operator_right-shift"><code class="literal">&gt;&gt;</code></a>, and
      <a class="link" href="functions.html#operator_bitwise-invert"><code class="literal">~</code></a> are not
      permitted in partitioning expressions.
    </p><p><b>Server SQL mode. </b>
        <a class="indexterm" name="idm139899482723968"></a>

        <a class="indexterm" name="idm139899482722480"></a>

        Tables employing user-defined partitioning do not preserve the
        SQL mode in effect at the time that they were created. As
        discussed elsewhere in this Manual (see
        <a class="xref" href="server-administration.html#sql-mode" title="5.1.8 Server SQL Modes">Section 5.1.8, “Server SQL Modes”</a>), the results of many MySQL functions
        and operators may change according to the server SQL mode.
        Therefore, a change in the SQL mode at any time after the
        creation of partitioned tables may lead to major changes in the
        behavior of such tables, and could easily lead to corruption or
        loss of data. For these reasons, <span class="emphasis"><em>it is strongly
        recommended that you never change the server SQL mode after
        creating partitioned tables</em></span>.
      </p><p><b>Examples. </b>
        The following examples illustrate some changes in behavior of
        partitioned tables due to a change in the server SQL mode:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p><b>Error handling. </b>
            As discussed elsewhere, handling of <span class="quote">“<span class="quote">special</span>”</span>
            values such as zero and <code class="literal">NULL</code> can differ
            between different server SQL modes (see
            <a class="xref" href="server-administration.html#sql-mode" title="5.1.8 Server SQL Modes">Section 5.1.8, “Server SQL Modes”</a>). For example,
            <a class="link" href="server-administration.html#sqlmode_error_for_division_by_zero"><code class="literal">ERROR_FOR_DIVISION_BY_ZERO</code></a>
            can affect whether or not 0 can be inserted as a value into
            a table whose paritioning expression uses
            <code class="literal"><em class="replaceable"><code>column</code></em> DIV
            <em class="replaceable"><code>value</code></em></code> or
            <code class="literal"><em class="replaceable"><code>column</code></em> MOD
            <em class="replaceable"><code>value</code></em>.</code>
          </p></li><li class="listitem"><p><b>Table accessibility. </b>
            Sometimes a change in the server SQL mode can make
            partitioned tables unusable. The following
            <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement can be
            executed successfully only if the
            <a class="link" href="server-administration.html#sqlmode_no_unsigned_subtraction"><code class="literal">NO_UNSIGNED_SUBTRACTION</code></a>
            mode is in effect:
          </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SELECT @@sql_mode;</code></strong>
+------------+
| @@sql_mode |
+------------+
|            |
+------------+
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>CREATE TABLE tu (c1 BIGINT UNSIGNED)</code></strong>
    -&gt;   <strong class="userinput"><code>PARTITION BY RANGE(c1 - 10) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (-5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (0),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (10),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p4 VALUES LESS THAN (MAXVALUE)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
<span class="errortext">ERROR 1563 (HY000): Partition constant is out of partition function domain</span>

mysql&gt; <strong class="userinput"><code>SET sql_mode='NO_UNSIGNED_SUBTRACTION';</code></strong>
Query OK, 0 rows affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT @@sql_mode;</code></strong>
+-------------------------+
| @@sql_mode              |
+-------------------------+
| NO_UNSIGNED_SUBTRACTION |
+-------------------------+
1 row in set (0.00 sec)

mysql&gt; <strong class="userinput"><code>CREATE TABLE tu (c1 BIGINT UNSIGNED)</code></strong>
    -&gt;   <strong class="userinput"><code>PARTITION BY RANGE(c1 - 10) (</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (-5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (0),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (10),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p4 VALUES LESS THAN (MAXVALUE)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.05 sec)
</pre><p>
          If you remove the
          <a class="link" href="server-administration.html#sqlmode_no_unsigned_subtraction"><code class="literal">NO_UNSIGNED_SUBTRACTION</code></a>
          server SQL mode after creating <code class="literal">tu</code>, you may
          no longer be able to access this table:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>SET sql_mode='';</code></strong>
Query OK, 0 rows affected (0.00 sec)

mysql&gt; <strong class="userinput"><code>SELECT * FROM tu;</code></strong>
<span class="errortext">ERROR 1563 (HY000): Partition constant is out of partition function domain</span>
mysql&gt; <strong class="userinput"><code>INSERT INTO tu VALUES (20);</code></strong>
<span class="errortext">ERROR 1563 (HY000): Partition constant is out of partition function domain</span>
</pre><p>
          See also <a class="xref" href="server-administration.html#sql-mode" title="5.1.8 Server SQL Modes">Section 5.1.8, “Server SQL Modes”</a>.
</p></li></ol>
</div>
<p>
      Server SQL modes also impact replication of partitioned tables.
      Disparate SQL modes on master and slave can lead to partitioning
      expressions being evaluated differently; this can cause the
      distribution of data among partitions to be different in the
      master's and slave's copies of a given table, and may
      even cause inserts into partitioned tables that succeed on the
      master to fail on the slave. For best results, you should always
      use the same server SQL mode on the master and on the slave.
    </p><p><b>Performance considerations. </b>
        Some effects of partitioning operations on performance are given
        in the following list:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p><a name="partitioning-limitations-file-system-ops"></a><b>File system operations. </b>
            Partitioning and repartitioning operations (such as
            <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
            TABLE</code></a> with <code class="literal">PARTITION BY ...</code>,
            <code class="literal">REORGANIZE PARTITION</code>, or <code class="literal">REMOVE
            PARTITIONING</code>) depend on file system operations for
            their implementation. This means that the speed of these
            operations is affected by such factors as file system type
            and characteristics, disk speed, swap space, file handling
            efficiency of the operating system, and MySQL server options
            and variables that relate to file handling. In particular,
            you should make sure that
            <a class="link" href="server-administration.html#sysvar_large_files_support"><code class="literal">large_files_support</code></a> is
            enabled and that
            <a class="link" href="server-administration.html#sysvar_open_files_limit"><code class="literal">open_files_limit</code></a> is set
            properly. Partitioning and repartitioning operations
            involving <code class="literal">InnoDB</code> tables may be made more
            efficient by enabling
            <a class="link" href="innodb-storage-engine.html#sysvar_innodb_file_per_table"><code class="literal">innodb_file_per_table</code></a>.
          </p><p>
          See also
          <a class="xref" href="partitioning.html#partitioning-limitations-max-partitions" title="Maximum number of partitions">Maximum number of partitions</a>.
        </p></li><li class="listitem"><p><b>Table locks. </b>
            Generally, the process executing a partitioning operation on
            a table takes a write lock on the table. Reads from such
            tables are relatively unaffected; pending
            <a class="link" href="sql-syntax.html#insert" title="13.2.6 INSERT Syntax"><code class="literal">INSERT</code></a> and
            <a class="link" href="sql-syntax.html#update" title="13.2.12 UPDATE Syntax"><code class="literal">UPDATE</code></a> operations are
            performed as soon as the partitioning operation has
            completed. For <code class="literal">InnoDB</code>-specific exceptions
            to this limitation, see
            <a class="xref" href="innodb-storage-engine.html#online-ddl-partitioning" title="15.12.6 Online DDL for Partitioned Tables">Section 15.12.6, “Online DDL for Partitioned Tables”</a>.
          </p></li><li class="listitem"><p><b>Indexes; partition pruning. </b>
            As with nonpartitioned tables, proper use of indexes can
            speed up queries on partitioned tables significantly. In
            addition, designing partitioned tables and queries on these
            tables to take advantage of
            <span class="firstterm">partition pruning</span> can
            improve performance dramatically. See
            <a class="xref" href="partitioning.html#partitioning-pruning" title="22.4 Partition Pruning">Section 22.4, “Partition Pruning”</a>, for more
            information.
          </p><p>
          Index condition pushdown is supported for partitioned tables.
          See <a class="xref" href="optimization.html#index-condition-pushdown-optimization" title="8.2.1.5 Index Condition Pushdown Optimization">Section 8.2.1.5, “Index Condition Pushdown Optimization”</a>.
        </p></li><li class="listitem"><p><b>Performance with LOAD DATA. </b>
            In MySQL 8.0, <a class="link" href="sql-syntax.html#load-data" title="13.2.7 LOAD DATA INFILE Syntax"><code class="literal">LOAD
            DATA</code></a> uses buffering to improve performance. You
            should be aware that the buffer uses 130 KB memory per
            partition to achieve this.
</p></li></ul>
</div>
<p><a name="partitioning-limitations-max-partitions"></a><b>Maximum number of partitions. </b><a class="indexterm" name="idm139899482651008"></a><a class="indexterm" name="idm139899482649504"></a>
        In MySQL 8.0, the maximum possible number of
        partitions for a given table is 8192. This number includes
        subpartitions.
      </p><p>
      If, when creating tables with a large number of partitions (but
      less than the maximum), you encounter an error message such as
      <span class="errortext">Got error ... from storage engine: Out of resources
      when opening file</span>, you may be able to address the
      issue by increasing the value of the
      <a class="link" href="server-administration.html#sysvar_open_files_limit"><code class="literal">open_files_limit</code></a> system variable.
      However, this is dependent on the operating system, and may not be
      possible or advisable on all platforms; see
      <a class="xref" href="error-handling.html#not-enough-file-handles" title="B.5.2.17 File Not Found and Similar Errors">Section B.5.2.17, “File Not Found and Similar Errors”</a>, for more information.
      In some cases, using large numbers (hundreds) of partitions may
      also not be advisable due to other concerns, so using more
      partitions does not automatically lead to better results.
    </p><p>
      See also
      <a class="xref" href="partitioning.html#partitioning-limitations-file-system-ops" title="File system operations">File system operations</a>.
    </p><p><b>Foreign keys not supported for partitioned InnoDB tables. </b><a class="indexterm" name="idm139899482641920"></a>
        Partitioned tables using the <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a>
        storage engine do not support foreign keys. More specifically,
        this means that the following two statements are true:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
          No definition of an <code class="literal">InnoDB</code> table employing
          user-defined partitioning may contain foreign key references;
          no <code class="literal">InnoDB</code> table whose definition contains
          foreign key references may be partitioned.
        </p></li><li class="listitem"><p>
          No <code class="literal">InnoDB</code> table definition may contain a
          foreign key reference to a user-partitioned table; no
          <code class="literal">InnoDB</code> table with user-defined partitioning
          may contain columns referenced by foreign keys.
</p></li></ol>
</div>
<p>
      The scope of the restrictions just listed includes all tables that
      use the <code class="literal">InnoDB</code> storage engine.
      <a class="link" href="sql-syntax.html#create-table-foreign-keys" title="13.1.18.6 Using FOREIGN KEY Constraints"><code class="literal">CREATE
      TABLE</code></a> and <a class="link" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax"><code class="literal">ALTER TABLE</code></a>
      statements that would result in tables violating these
      restrictions are not allowed.
    </p><p><b>ALTER TABLE ... ORDER BY. </b>
        An <code class="literal">ALTER TABLE ... ORDER BY
        <em class="replaceable"><code>column</code></em></code> statement run
        against a partitioned table causes ordering of rows only within
        each partition.
      </p><p><b>Effects on REPLACE statements by modification of primary keys. </b>
        It can be desirable in some cases (see
        <a class="xref" href="partitioning.html#partitioning-limitations-partitioning-keys-unique-keys" title="22.6.1 Partitioning Keys, Primary Keys, and Unique Keys">Section 22.6.1, “Partitioning Keys, Primary Keys, and Unique Keys”</a>)
        to modify a table's primary key. Be aware that, if your
        application uses <a class="link" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax"><code class="literal">REPLACE</code></a>
        statements and you do this, the results of these statements can
        be drastically altered. See <a class="xref" href="sql-syntax.html#replace" title="13.2.9 REPLACE Syntax">Section 13.2.9, “REPLACE Syntax”</a>, for more
        information and an example.
      </p><p><b>FULLTEXT indexes. </b><a class="indexterm" name="idm139899482621824"></a>
        Partitioned tables do not support <code class="literal">FULLTEXT</code>
        indexes or searches.
      </p><p><b>Spatial columns. </b>
        Columns with spatial data types such as <code class="literal">POINT</code>
        or <code class="literal">GEOMETRY</code> cannot be used in partitioned
        tables.
      </p><p><b>Temporary tables. </b><a class="indexterm" name="idm139899482615632"></a>
        Temporary tables cannot be partitioned.
      </p><p><b>Log tables. </b>
        It is not possible to partition the log tables; an
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... PARTITION BY ...</code></a> statement on such a table
        fails with an error.
      </p><p><b>Data type of partitioning key. </b><a class="indexterm" name="idm139899482610272"></a>
        A partitioning key must be either an integer column or an
        expression that resolves to an integer. Expressions employing
        <a class="link" href="data-types.html#enum" title="11.4.4 The ENUM Type"><code class="literal">ENUM</code></a> columns cannot be used. The
        column or expression value may also be <code class="literal">NULL</code>;
        see <a class="xref" href="partitioning.html#partitioning-handling-nulls" title="22.2.7 How MySQL Partitioning Handles NULL">Section 22.2.7, “How MySQL Partitioning Handles NULL”</a>.
      </p><p>
      There are two exceptions to this restriction:
</p>
<div class="orderedlist">
<ol class="orderedlist" type="1"><li class="listitem"><p>
          When partitioning by [<code class="literal">LINEAR</code>]
          <code class="literal">KEY</code>, it is possible to use columns of any
          valid MySQL data type other than
          <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">TEXT</code></a> or
          <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">BLOB</code></a> as partitioning keys,
          because the internal key-hashing functions produce the correct
          data type from these types. For example, the following two
          <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statements are
          valid:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE tkc (c1 CHAR)
PARTITION BY KEY(c1)
PARTITIONS 4;

CREATE TABLE tke
    ( c1 ENUM('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet') )
PARTITION BY LINEAR KEY(c1)
PARTITIONS 6;
</pre></li><li class="listitem"><p>
          When partitioning by <code class="literal">RANGE COLUMNS</code> or
          <code class="literal">LIST COLUMNS</code>, it is possible to use string,
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATE</code></a>, and
          <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">DATETIME</code></a> columns. For example,
          each of the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
          TABLE</code></a> statements is valid:
        </p><pre data-lang="sql" class="programlisting">
CREATE TABLE rc (c1 INT, c2 DATE)
PARTITION BY RANGE COLUMNS(c2) (
    PARTITION p0 VALUES LESS THAN('1990-01-01'),
    PARTITION p1 VALUES LESS THAN('1995-01-01'),
    PARTITION p2 VALUES LESS THAN('2000-01-01'),
    PARTITION p3 VALUES LESS THAN('2005-01-01'),
    PARTITION p4 VALUES LESS THAN(MAXVALUE)
);

CREATE TABLE lc (c1 INT, c2 CHAR(1))
PARTITION BY LIST COLUMNS(c2) (
    PARTITION p0 VALUES IN('a', 'd', 'g', 'j', 'm', 'p', 's', 'v', 'y'),
    PARTITION p1 VALUES IN('b', 'e', 'h', 'k', 'n', 'q', 't', 'w', 'z'),
    PARTITION p2 VALUES IN('c', 'f', 'i', 'l', 'o', 'r', 'u', 'x', NULL)
);
</pre></li></ol>
</div>
<p>
      Neither of the preceding exceptions applies to
      <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">BLOB</code></a> or
      <a class="link" href="data-types.html#blob" title="11.4.3 The BLOB and TEXT Types"><code class="literal">TEXT</code></a> column types.
    </p><p><b>Subqueries. </b><a class="indexterm" name="idm139899482585520"></a>
        A partitioning key may not be a subquery, even if that subquery
        resolves to an integer value or <code class="literal">NULL</code>.
      </p><p><a name="partitioning-limitations-subpartitions"></a><b>Issues with subpartitions. </b><a class="indexterm" name="idm139899482581520"></a><a class="indexterm" name="idm139899482580032"></a>
        Subpartitions must use <code class="literal">HASH</code> or
        <code class="literal">KEY</code> partitioning. Only
        <code class="literal">RANGE</code> and <code class="literal">LIST</code> partitions
        may be subpartitioned; <code class="literal">HASH</code> and
        <code class="literal">KEY</code> partitions cannot be subpartitioned.
      </p><p>
      <a class="indexterm" name="idm139899482573392"></a>

      <code class="literal">SUBPARTITION BY KEY</code> requires that the
      subpartitioning column or columns be specified explicitly, unlike
      the case with <code class="literal">PARTITION BY KEY</code>, where it can be
      omitted (in which case the table's primary key column is used
      by default). Consider the table created by this statement:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30)
);
</pre><p>
      You can create a table having the same columns, partitioned by
      <code class="literal">KEY</code>, using a statement such as this one:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30)
)
PARTITION BY KEY()
PARTITIONS 4;
</pre><p>
      The previous statement is treated as though it had been written
      like this, with the table's primary key column used as the
      partitioning column:
    </p><pre data-lang="sql" class="programlisting">
CREATE TABLE ts (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30)
)
PARTITION BY KEY(id)
PARTITIONS 4;
        </pre><p>
      However, the following statement that attempts to create a
      subpartitioned table using the default column as the
      subpartitioning column fails, and the column must be specified for
      the statement to succeed, as shown here:
    </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE ts (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,</code></strong>
    -&gt;     <strong class="userinput"><code>name VARCHAR(30)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE(id)</code></strong>
    -&gt; <strong class="userinput"><code>SUBPARTITION BY KEY()</code></strong>
    -&gt; <strong class="userinput"><code>SUBPARTITIONS 4</code></strong>
    -&gt; <strong class="userinput"><code>(</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (100),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (MAXVALUE)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
<span class="errortext">ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near ')</span>

mysql&gt; <strong class="userinput"><code>CREATE TABLE ts (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,</code></strong>
    -&gt;     <strong class="userinput"><code>name VARCHAR(30)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY RANGE(id)</code></strong>
    -&gt; <strong class="userinput"><code>SUBPARTITION BY KEY(id)</code></strong>
    -&gt; <strong class="userinput"><code>SUBPARTITIONS 4</code></strong>
    -&gt; <strong class="userinput"><code>(</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (100),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (MAXVALUE)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.07 sec)
</pre><p>
      This is a known issue (see Bug #51470).
    </p><p><b>DATA DIRECTORY and INDEX DIRECTORY options. </b>
        Table-level <code class="literal">DATA DIRECTORY</code> and <code class="literal">INDEX
        DIRECTORY</code> options are ignored (see Bug #32091). You
        can employ these options for individual partitions or
        subpartitions of <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a> tables.
      </p><p><b>Repairing and rebuilding partitioned tables. </b>
        The statements <a class="link" href="sql-syntax.html#check-table" title="13.7.3.2 CHECK TABLE Syntax"><code class="literal">CHECK TABLE</code></a>,
        <a class="link" href="sql-syntax.html#optimize-table" title="13.7.3.4 OPTIMIZE TABLE Syntax"><code class="literal">OPTIMIZE TABLE</code></a>,
        <a class="link" href="sql-syntax.html#analyze-table" title="13.7.3.1 ANALYZE TABLE Syntax"><code class="literal">ANALYZE TABLE</code></a>, and
        <a class="link" href="sql-syntax.html#repair-table" title="13.7.3.5 REPAIR TABLE Syntax"><code class="literal">REPAIR TABLE</code></a> are supported for
        partitioned tables.
      </p><p>
      In addition, you can use <code class="literal">ALTER TABLE ... REBUILD
      PARTITION</code> to rebuild one or more partitions of a
      partitioned table; <code class="literal">ALTER TABLE ... REORGANIZE
      PARTITION</code> also causes partitions to be rebuilt. See
      <a class="xref" href="sql-syntax.html#alter-table" title="13.1.8 ALTER TABLE Syntax">Section 13.1.8, “ALTER TABLE Syntax”</a>, for more information about these
      two statements.
    </p><p>
      <code class="literal">ANALYZE</code>, <code class="literal">CHECK</code>,
      <code class="literal">OPTIMIZE</code>, <code class="literal">REPAIR</code>, and
      <code class="literal">TRUNCATE</code> operations are supported with
      subpartitions. See
      <a class="xref" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations">Section 13.1.8.1, “ALTER TABLE Partition Operations”</a>.
</p>
<div class="section">

<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-limitations-partitioning-keys-unique-keys"></a>22.6.1 Partitioning Keys, Primary Keys, and Unique Keys</h3>
</div>
</div>
</div>
<a class="indexterm" name="idm139899482527936"></a><a class="indexterm" name="idm139899482526896"></a><a class="indexterm" name="idm139899482525408"></a><a class="indexterm" name="idm139899482524320"></a><a class="indexterm" name="idm139899482522832"></a><p>
        This section discusses the relationship of partitioning keys
        with primary keys and unique keys. The rule governing this
        relationship can be expressed as follows: All columns used in
        the partitioning expression for a partitioned table must be part
        of every unique key that the table may have.
      </p><p>
        In other words, <span class="emphasis"><em>every unique key on the table must use
        every column in the table's partitioning
        expression</em></span>. (This also includes the table's
        primary key, since it is by definition a unique key. This
        particular case is discussed later in this section.) For
        example, each of the following table creation statements is
        invalid:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t1 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY (col1, col2)
)
PARTITION BY HASH(col3)
PARTITIONS 4;

CREATE TABLE t2 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY (col1),
    UNIQUE KEY (col3)
)
PARTITION BY HASH(col1 + col3)
PARTITIONS 4;
</pre><p>
        In each case, the proposed table would have at least one unique
        key that does not include all columns used in the partitioning
        expression.
      </p><p>
        Each of the following statements is valid, and represents one
        way in which the corresponding invalid table creation statement
        could be made to work:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t1 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY (col1, col2, col3)
)
PARTITION BY HASH(col3)
PARTITIONS 4;

CREATE TABLE t2 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY (col1, col3)
)
PARTITION BY HASH(col1 + col3)
PARTITIONS 4;
</pre><p>
        This example shows the error produced in such cases:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t3 (</code></strong>
    -&gt;     <strong class="userinput"><code>col1 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col2 DATE NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col3 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col4 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>UNIQUE KEY (col1, col2),</code></strong>
    -&gt;     <strong class="userinput"><code>UNIQUE KEY (col3)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY HASH(col1 + col3)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITIONS 4;</code></strong>
<span class="errortext">ERROR 1491 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function</span>
</pre><p>
        The <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE TABLE</code></a> statement fails
        because both <code class="literal">col1</code> and <code class="literal">col3</code>
        are included in the proposed partitioning key, but neither of
        these columns is part of both of unique keys on the table. This
        shows one possible fix for the invalid table definition:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t3 (</code></strong>
    -&gt;     <strong class="userinput"><code>col1 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col2 DATE NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col3 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>col4 INT NOT NULL,</code></strong>
    -&gt;     <strong class="userinput"><code>UNIQUE KEY (col1, col2, col3),</code></strong>
    -&gt;     <strong class="userinput"><code>UNIQUE KEY (col3)</code></strong>
    -&gt; <strong class="userinput"><code>)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITION BY HASH(col3)</code></strong>
    -&gt; <strong class="userinput"><code>PARTITIONS 4;</code></strong>
Query OK, 0 rows affected (0.05 sec)
</pre><p>
        In this case, the proposed partitioning key
        <code class="literal">col3</code> is part of both unique keys, and the
        table creation statement succeeds.
      </p><p>
        The following table cannot be partitioned at all, because there
        is no way to include in a partitioning key any columns that
        belong to both unique keys:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t4 (
    col1 INT NOT NULL,
    col2 INT NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY (col1, col3),
    UNIQUE KEY (col2, col4)
);
</pre><p>
        Since every primary key is by definition a unique key, this
        restriction also includes the table's primary key, if it
        has one. For example, the next two statements are invalid:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t5 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    PRIMARY KEY(col1, col2)
)
PARTITION BY HASH(col3)
PARTITIONS 4;

CREATE TABLE t6 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    PRIMARY KEY(col1, col3),
    UNIQUE KEY(col2)
)
PARTITION BY HASH( YEAR(col2) )
PARTITIONS 4;
</pre><p>
        In both cases, the primary key does not include all columns
        referenced in the partitioning expression. However, both of the
        next two statements are valid:
      </p><pre data-lang="sql" class="programlisting">
CREATE TABLE t7 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    PRIMARY KEY(col1, col2)
)
PARTITION BY HASH(col1 + YEAR(col2))
PARTITIONS 4;

CREATE TABLE t8 (
    col1 INT NOT NULL,
    col2 DATE NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    PRIMARY KEY(col1, col2, col4),
    UNIQUE KEY(col2, col1)
)
PARTITION BY HASH(col1 + YEAR(col2))
PARTITIONS 4;
</pre><p>
        If a table has no unique keys—this includes having no
        primary key—then this restriction does not apply, and you
        may use any column or columns in the partitioning expression as
        long as the column type is compatible with the partitioning
        type.
      </p><p>
        For the same reason, you cannot later add a unique key to a
        partitioned table unless the key includes all columns used by
        the table's partitioning expression. Consider the partitioned
        table created as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t_no_pk (c1 INT, c2 INT)</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION BY RANGE(c1) (</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p0 VALUES LESS THAN (10),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p1 VALUES LESS THAN (20),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p2 VALUES LESS THAN (30),</code></strong>
    -&gt;         <strong class="userinput"><code>PARTITION p3 VALUES LESS THAN (40)</code></strong>
    -&gt;     <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.12 sec)
</pre><p>
        It is possible to add a primary key to
        <code class="literal">t_no_pk</code> using either of these
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statements:
      </p><pre data-lang="sql" class="programlisting">
#  possible PK
mysql&gt; <strong class="userinput"><code>ALTER TABLE t_no_pk ADD PRIMARY KEY(c1);</code></strong>
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

# drop this PK
mysql&gt; <strong class="userinput"><code>ALTER TABLE t_no_pk DROP PRIMARY KEY;</code></strong>
Query OK, 0 rows affected (0.10 sec)
Records: 0  Duplicates: 0  Warnings: 0

#  use another possible PK
mysql&gt; <strong class="userinput"><code>ALTER TABLE t_no_pk ADD PRIMARY KEY(c1, c2);</code></strong>
Query OK, 0 rows affected (0.12 sec)
Records: 0  Duplicates: 0  Warnings: 0

# drop this PK
mysql&gt; <strong class="userinput"><code>ALTER TABLE t_no_pk DROP PRIMARY KEY;</code></strong>
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0
</pre><p>
        However, the next statement fails, because <code class="literal">c1</code>
        is part of the partitioning key, but is not part of the proposed
        primary key:
      </p><pre data-lang="sql" class="programlisting">
#  fails with error 1503
mysql&gt; <strong class="userinput"><code>ALTER TABLE t_no_pk ADD PRIMARY KEY(c2);</code></strong>
<span class="errortext">ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function</span>
</pre><p>
        Since <code class="literal">t_no_pk</code> has only <code class="literal">c1</code>
        in its partitioning expression, attempting to adding a unique
        key on <code class="literal">c2</code> alone fails. However, you can add a
        unique key that uses both <code class="literal">c1</code> and
        <code class="literal">c2</code>.
      </p><p>
        These rules also apply to existing nonpartitioned tables that
        you wish to partition using
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... PARTITION BY</code></a>. Consider a table
        <code class="literal">np_pk</code> created as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE np_pk (</code></strong>
    -&gt;     <strong class="userinput"><code>id INT NOT NULL AUTO_INCREMENT,</code></strong>
    -&gt;     <strong class="userinput"><code>name VARCHAR(50),</code></strong>
    -&gt;     <strong class="userinput"><code>added DATE,</code></strong>
    -&gt;     <strong class="userinput"><code>PRIMARY KEY (id)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
Query OK, 0 rows affected (0.08 sec)
</pre><p>
        The following
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE</code></a> statement fails with an error, because the
        <code class="literal">added</code> column is not part of any unique key in
        the table:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE np_pk</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION BY HASH( TO_DAYS(added) )</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITIONS 4;</code></strong>
<span class="errortext">ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function</span>
</pre><p>
        However, this statement using the <code class="literal">id</code> column
        for the partitioning column is valid, as shown here:
      </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>ALTER TABLE np_pk</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION BY HASH(id)</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITIONS 4;</code></strong>
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0
</pre><p>
        In the case of <code class="literal">np_pk</code>, the only column that
        may be used as part of a partitioning expression is
        <code class="literal">id</code>; if you wish to partition this table using
        any other column or columns in the partitioning expression, you
        must first modify the table, either by adding the desired column
        or columns to the primary key, or by dropping the primary key
        altogether.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-limitations-storage-engines"></a>22.6.2 Partitioning Limitations Relating to Storage Engines</h3>

</div>

</div>

</div>
<a class="indexterm" name="idm139899482444080"></a><p>
        In MySQL 8.0, partitioning support is not actually
        provided by the MySQL Server, but rather by a table storage
        engine's own or native partitioning handler. In MySQL
        8.0, only the <a class="link" href="innodb-storage-engine.html" title="Chapter 15 The InnoDB Storage Engine"><code class="literal">InnoDB</code></a>
        storage engine provides a native partitioning handler. This
        means that partitioned tables cannot be created using any other
        storage engine.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">

<div class="admon-title">
Note
</div>
<p>
          MySQL Cluster's <code class="literal">NDB</code> storage engine
          also provides native partitioning support, but is not
          currently supported in MySQL 8.0.
</p>
</div>
<p>
        <a class="link" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations"><code class="literal">ALTER
        TABLE ... OPTIMIZE PARTITION</code></a> does not work correctly
        with partitioned tables that use <code class="literal">InnoDB</code>. Use
        <code class="literal">ALTER TABLE ... REBUILD PARTITION</code> and
        <code class="literal">ALTER TABLE ... ANALYZE PARTITION</code>, instead,
        for such tables. For more information, see
        <a class="xref" href="sql-syntax.html#alter-table-partition-operations" title="13.1.8.1 ALTER TABLE Partition Operations">Section 13.1.8.1, “ALTER TABLE Partition Operations”</a>.
      </p><p><b>Upgrading partitioned tables. </b>
          When performing an upgrade, tables which are partitioned by
          <code class="literal">KEY</code> must be dumped and reloaded.
          Partitioned tables using storage engines other than
          <code class="literal">InnoDB</code> cannot be upgraded from MySQL 5.7 or
          earlier to MySQL 8.0 or later; you must either drop the
          partitioning from such tables with <code class="literal">ALTER TABLE ...
          REMOVE PARTITIONING</code> or convert them to
          <code class="literal">InnoDB</code> using <code class="literal">ALTER TABLE ...
          ENGINE=INNODB</code> prior to the upgrade.
        </p><p>
        For information about converting <code class="literal">MyISAM</code>
        tables to <code class="literal">InnoDB</code>, see
        <a class="xref" href="innodb-storage-engine.html#converting-tables-to-innodb" title="15.8.1.4 Converting Tables from MyISAM to InnoDB">Section 15.8.1.4, “Converting Tables from MyISAM to InnoDB”</a>.
</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a name="partitioning-limitations-functions"></a>22.6.3 Partitioning Limitations Relating to Functions</h3>

</div>

</div>

</div>
<p>
        This section discusses limitations in MySQL Partitioning
        relating specifically to functions used in partitioning
        expressions.
      </p><a class="indexterm" name="idm139899482423872"></a><p>
        Only the MySQL functions shown in the following list are allowed
        in partitioning expressions:
</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>
            <a class="link" href="functions.html#function_abs"><code class="literal">ABS()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_ceiling"><code class="literal">CEILING()</code></a> (see
            <a class="xref" href="partitioning.html#partitioning-limitations-ceiling-floor" title="CEILING() and FLOOR()">CEILING() and FLOOR()</a>)
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_datediff"><code class="literal">DATEDIFF()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_day"><code class="literal">DAY()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_dayofmonth"><code class="literal">DAYOFMONTH()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_dayofweek"><code class="literal">DAYOFWEEK()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_dayofyear"><code class="literal">DAYOFYEAR()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_extract"><code class="literal">EXTRACT()</code></a> (see
            <a class="xref" href="partitioning.html#partitioning-limitations-extract" title="EXTRACT() function with WEEK specifier">EXTRACT() function with WEEK specifier</a>)
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_floor"><code class="literal">FLOOR()</code></a> (see
            <a class="xref" href="partitioning.html#partitioning-limitations-ceiling-floor" title="CEILING() and FLOOR()">CEILING() and FLOOR()</a>)
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_hour"><code class="literal">HOUR()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_microsecond"><code class="literal">MICROSECOND()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_minute"><code class="literal">MINUTE()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_mod"><code class="literal">MOD()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_month"><code class="literal">MONTH()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_quarter"><code class="literal">QUARTER()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_second"><code class="literal">SECOND()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_time-to-sec"><code class="literal">TIME_TO_SEC()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_to-seconds"><code class="literal">TO_SECONDS()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_unix-timestamp"><code class="literal">UNIX_TIMESTAMP()</code></a> (with
            <a class="link" href="data-types.html#datetime" title="11.3.1 The DATE, DATETIME, and TIMESTAMP Types"><code class="literal">TIMESTAMP</code></a> columns)
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_weekday"><code class="literal">WEEKDAY()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_year"><code class="literal">YEAR()</code></a>
          </p></li><li class="listitem"><p>
            <a class="link" href="functions.html#function_yearweek"><code class="literal">YEARWEEK()</code></a>
</p></li></ul>
</div>
<p>
        In MySQL 8.0, partition pruning is supported for
        the <a class="link" href="functions.html#function_to-days"><code class="literal">TO_DAYS()</code></a>,
        <a class="link" href="functions.html#function_to-seconds"><code class="literal">TO_SECONDS()</code></a>,
        <a class="link" href="functions.html#function_year"><code class="literal">YEAR()</code></a>, and
        <a class="link" href="functions.html#function_unix-timestamp"><code class="literal">UNIX_TIMESTAMP()</code></a> functions. See
        <a class="xref" href="partitioning.html#partitioning-pruning" title="22.4 Partition Pruning">Section 22.4, “Partition Pruning”</a>, for more information.
      </p><p><a name="partitioning-limitations-ceiling-floor"></a><b>CEILING() and FLOOR(). </b>
          Each of these functions returns an integer only if it is
          passed an argument of an exact numeric type, such as one of
          the <a class="link" href="data-types.html#integer-types" title="11.2.1 Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"><code class="literal">INT</code></a> types or
          <a class="link" href="data-types.html#fixed-point-types" title="11.2.2 Fixed-Point Types (Exact Value) - DECIMAL, NUMERIC"><code class="literal">DECIMAL</code></a>. This means, for
          example, that the following <a class="link" href="sql-syntax.html#create-table" title="13.1.18 CREATE TABLE Syntax"><code class="literal">CREATE
          TABLE</code></a> statement fails with an error, as shown here:
        </p><pre data-lang="sql" class="programlisting">
mysql&gt; <strong class="userinput"><code>CREATE TABLE t (c FLOAT) PARTITION BY LIST( FLOOR(c) )(</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p0 VALUES IN (1,3,5),</code></strong>
    -&gt;     <strong class="userinput"><code>PARTITION p1 VALUES IN (2,4,6)</code></strong>
    -&gt; <strong class="userinput"><code>);</code></strong>
<span class="errortext">ERROR 1490 (HY000): The PARTITION function returns the wrong type</span>
</pre><p><a name="partitioning-limitations-extract"></a><b>EXTRACT() function with WEEK specifier. </b>
          The value returned by the
          <a class="link" href="functions.html#function_extract"><code class="literal">EXTRACT()</code></a> function, when used
          as <a class="link" href="functions.html#function_extract"><code class="literal">EXTRACT(WEEK FROM
          <em class="replaceable"><code>col</code></em>)</code></a>, depends on the
          value of the
          <a class="link" href="server-administration.html#sysvar_default_week_format"><code class="literal">default_week_format</code></a> system
          variable. For this reason,
          <a class="link" href="functions.html#function_extract"><code class="literal">EXTRACT()</code></a> is not permitted as a
          partitioning function when it specifies the unit as
          <code class="literal">WEEK</code>. (Bug #54483)
        </p><p>
        See <a class="xref" href="functions.html#mathematical-functions" title="12.6.2 Mathematical Functions">Section 12.6.2, “Mathematical Functions”</a>, for more
        information about the return types of these functions, as well
        as <a class="xref" href="data-types.html#numeric-types" title="11.2 Numeric Types">Section 11.2, “Numeric Types”</a>.
</p>
</div>

</div>

</div>
<div class="copyright-footer">

</div>
<div class="navfooter">
<hr>
<table width="100%" summary="Navigation footer">
<tr>
<td width="40%" align="left"><a accesskey="p" href="mysql-innodb-cluster-userguide.html">Prev</a></td>
<td width="20%" align="center"><a accesskey="u" href="">Up</a></td>
<td width="40%" align="right"> <a accesskey="n" href="stored-programs-views.html">Next</a></td>
</tr>
<tr>
<td width="40%" align="left" valign="top">Chapter 21 InnoDB Cluster</td>
<td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td>
<td width="40%" align="right" valign="top">Chapter 23 Stored Programs and Views</td>
</tr>
</table>
</div>
</body>
</html>
